Search code examples
html5-canvas

js canvas create pattern repeat issues


Currently drawing this onto a canvas:

enter image description here

I want to use a texture that repeats only on the Y axis where the red rectangle is.

However, when drawing the texture, nothing shows up when repeat-y is specified. It works when the repeat value is repeat however.

ctx.fillStyle = ctx.createPattern(assets.grassRightTexture, 'repeat-y');
ctx.fillRect(dimensions.left + dimensions.width, dimensions.top, 5, dimensions.height);

The assets.grassRightTexture is a preloaded img element with a 4px by 32px sprite.

Not sure if im doing something wrong but I didnt notice anything when reading the moz canvas docs.

Thank you.


Solution

  • The CanvasPattern object is always relative to the context's current transformation matrix (CTM) and not to the shape where you expect it to be drawn.

    So here, since you do create your pattern from an <img> element that has its width set to 4px, your CanvasPattern instance will cover only the rectangle from the coordinates (0, -∞) to (4, ∞).
    Since the rectangle your draw doesn't cover this area, you're filling with transparent pixels.

    const canvas = document.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    // prepare the CanvasPattern
    canvas.width = 4;
    canvas.height = 32;
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    const pattern = ctx.createPattern(canvas, "repeat-y");
    // try to use the pattern
    canvas.width = 300;
    canvas.height = 150;
    ctx.fillStyle = pattern;
    ctx.fillRect(24, 50, 50, 50); // doesn't draw anything
    ctx.strokeRect(24, 50, 50, 50); // to show we did draw something
    
    setTimeout(() => {
      ctx.fillRect(0, 75, 50, 50); // doesn't draw anything
      ctx.strokeRect(0, 75, 50, 50); // to show we did draw something
    }, 1000);
    <canvas></canvas>

    To overcome this you can either transform your CanvasPattern object thanks to its setTransform() method,

    const canvas = document.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    // prepare the CanvasPattern
    canvas.width = 4;
    canvas.height = 32;
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    const pattern = ctx.createPattern(canvas, "repeat-y");
    // try to use the pattern
    canvas.width = 300;
    canvas.height = 150;
    ctx.fillStyle = pattern;
    // we translate the CanvasPattern so that our shape is covered
    // setTransform accepts a DOMMatrixInit dictionary
    // `e` represents the x-axis translate
    pattern.setTransform({e: 24}); 
    ctx.fillRect(24, 50, 50, 50);
    ctx.strokeRect(24, 50, 50, 50);
    <canvas></canvas>

    or you can draw on your context in two steps with 2 different CTM for tracing the shape and for painting it.

    const canvas = document.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    // prepare the CanvasPattern
    canvas.width = 4;
    canvas.height = 32;
    ctx.fillStyle = "red";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    const pattern = ctx.createPattern(canvas, "repeat-y");
    // try to use the pattern
    canvas.width = 300;
    canvas.height = 150;
    ctx.fillStyle = pattern;
    // we trace at identity CTM
    ctx.rect(24, 50, 50, 50);
    // we move only the fillStyle & strokeStyle
    ctx.translate(24, 0);
    ctx.fill();
    ctx.stroke();
    <canvas></canvas>

    Though there is a bug in Firefox that makes this second option fail there.