hzqij1978
hzqij1978

Reputation: 283

HTML5 canvas createPattern API

I have code like:

<body>
    <canvas id="main" width=400 height=300></canvas>
<script>
var windowToCanvas = function(canvas, x, y) {
    var bbox = canvas.getBoundingClientRect();
    return {
        x: (x - bbox.left) * (canvas.width  / bbox.width),
        y: (y - bbox.top)  * (canvas.height / bbox.height)
    };
};

image = new Image();
image.src = "redball.png";
image.onload = function (e) {
    var canvas = document.getElementById('main'),
        context = canvas.getContext('2d');

    var pattern = context.createPattern(image, "repeat");

    function draw(loc) {
        context.clearRect(0, 0, canvas.width, canvas.height);
        context.fillStyle = pattern;
        context.beginPath();
        context.moveTo(loc.x, loc.y);
        context.lineTo(loc.x + 300, loc.y + 60);
        context.lineTo(loc.x + 70, loc.y + 200);
        context.closePath();
        context.fill();
    }

    canvas.onmousemove = function(e) {
        var event = e || window.event,
            x = event.x || event.clientX,
            y = event.y || event.clientY,
            loc = windowToCanvas(canvas, x, y);

        draw(loc);
    };
}
</script>
</body>

I call createPattern API, use a background image to fill a Triangle, but when mouse move, the background image also move, I only want the background image at fixed position, how Can I fix this?

Upvotes: 0

Views: 567

Answers (1)

markE
markE

Reputation: 105035

Think of a context pattern as a background image on the canvas.

Patterns always begin at the canvas origin [0,0]. If the pattern repeats, then the pattern fills the canvas in tiles repeating rightward and downward.

Therefore, your triangle will always reveal a different portion of the pattern if you move the triangle around the canvas.

There are multiple ways of having your triangle always reveal the same portion of the pattern image.

Option#1 -- context.translate

Move the canvas origin from its default [0,0] position to your triangle position [loc.x,loc.y]. You can do this with canvas transformations. In particular, the translate command will move the origin. Moving the origin will also move the top-left starting position of your pattern so that the pattern always aligns the same way relative to your moving triangle:

var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;

function draw(loc) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    // the origin [0,0] is now [loc.x,loc.y]
    context.translate(loc.x,loc.y);
    context.beginPath();
    // you are already located at [loc.x,loc.y] so
    // you don't need to add loc.x & loc.y to
    // your drawing coordinates
    context.moveTo(0,0);
    context.lineTo(300,60);
    context.lineTo(70,200);
    context.closePath();
    context.fill();
    // always clean up! Move the origina back to [0,0]
    context.translate(-loc.x,-loc.y);
}

A Demo using translate:

var windowToCanvas = function(canvas, x, y) {
  var bbox = canvas.getBoundingClientRect();
  return {
    x: (x - bbox.left) * (canvas.width  / bbox.width),
    y: (y - bbox.top)  * (canvas.height / bbox.height)
  };
};

image = new Image();
image.src = "https://dl.dropboxusercontent.com/u/139992952/multple/jellybeans.jpg";
image.onload = function (e) {
  var canvas = document.getElementById('main'),
      context = canvas.getContext('2d');

  var pattern = context.createPattern(image, "repeat");
  context.fillStyle=pattern;

  draw({x:0,y:0});

  function draw(loc) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    // the origin [0,0] is now [loc.x,loc.y]
    context.translate(loc.x,loc.y);
    context.beginPath();
    // you are already located at [loc.x,loc.y] so
    // you don't need to add loc.x & loc.y to
    // your drawing coordinates
    context.moveTo(0,0);
    context.lineTo(300,60);
    context.lineTo(70,200);
    context.closePath();
    context.fill();
    // always clean up! Move the origina back to [0,0]
    context.translate(-loc.x,-loc.y);
  }


  canvas.onmousemove = function(e) {
    var event = e || window.event,
        x = event.x || event.clientX,
        y = event.y || event.clientY,
        loc = windowToCanvas(canvas, x, y);

    draw(loc);
  };
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Move the mouse to move the triangle<br>The image is large, so be patient while it loads</h4>
<canvas id="main" width=600 height=600></canvas>

Option#2 -- compositing

Use compositing instead of patterning to draw an image atop your triangle. Compositing is a method to control how new pixels to be drawn on the canvas will interact with already existing canvas pixels. In particular, source-atop compositing will cause any new pixels to only be drawn where the new pixel overlaps an existing non-transparent pixel. What you would do is draw your triangle in a solid color and then use source-atop compositing to draw your image only where the solid triangle pixels are:

function draw(loc) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.beginPath();
    context.moveTo(loc.x, loc.y);
    context.lineTo(loc.x + 300, loc.y + 60);
    context.lineTo(loc.x + 70, loc.y + 200);
    context.closePath();
    // fill the triangle with a solid color
    context.fill();
    // set compositing to 'source-atop' so
    // new drawings will only be visible if
    // they overlap a solid color pixel
    context.globalCompositeOperation='source-atop';
    context.drawImage(image,loc.x,loc.y);
    // always clean up! Set compositing back to its default value
    context.globalCompositeOperation='source-over';
}

A Demo using compositing:

var windowToCanvas = function(canvas, x, y) {
  var bbox = canvas.getBoundingClientRect();
  return {
    x: (x - bbox.left) * (canvas.width  / bbox.width),
    y: (y - bbox.top)  * (canvas.height / bbox.height)
  };
};

image = new Image();
image.src = "https://dl.dropboxusercontent.com/u/139992952/multple/jellybeans.jpg";
image.onload = function (e) {
  var canvas = document.getElementById('main'),
      context = canvas.getContext('2d');

  var pattern = context.createPattern(image, "repeat");
  context.fillStyle=pattern;

  draw({x:0,y:0});

  function draw(loc) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.beginPath();
    context.moveTo(loc.x, loc.y);
    context.lineTo(loc.x + 300, loc.y + 60);
    context.lineTo(loc.x + 70, loc.y + 200);
    context.closePath();
    // fill the triangle with a solid color
    context.fill();
    // set compositing to 'source-atop' so
    // new drawings will only be visible if
    // they overlap a solid color pixel
    context.globalCompositeOperation='source-atop';
    context.drawImage(image,loc.x,loc.y);
    // always clean up! Set compositing back to its default value
    context.globalCompositeOperation='source-over';
  }

  canvas.onmousemove = function(e) {
    var event = e || window.event,
        x = event.x || event.clientX,
        y = event.y || event.clientY,
        loc = windowToCanvas(canvas, x, y);

    draw(loc);
  };
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Move the mouse to move the triangle<br>The image is large, so be patient while it loads</h4>
<canvas id="main" width=600 height=600></canvas>

More Options...

There are more possible options, too. I'll mention some of them without giving code examples:

  • Create an img element of your filled triangle and use drawImage(img,loc.x,loc.y) to move that triangle-image around the canvas

  • Create a clipping region from your triangle. Clipping regions cause new drawings to only be displayed in the defined clipping region. In this case, the new drawImage would only be visible inside your triangle shape and would not be visible outside your triangle.

  • And more options that are less conventional...

Upvotes: 1

Related Questions