ab_Dominoble_dev
ab_Dominoble_dev

Reputation: 111

Hatching effect inside HTML canvas rectangle

Background

I am working with browsers HTML5 canvas element.

I have code that (I've found online) that allows me to draw a rectangle to this canvas.

Question

I'd like to apply a 'hatching' effect like shown in the image, while the mouse move event is firing. How can this be achieved?

Current Method

My current method is to check the x and y coordinate of the mouse when the mouseMove event handler is fired. If the difference between the x and y coordinate compared to the original coordinate is greater than some predefined increment or ratio to the width and height of the rectangle, I will attempt to draw straight line/s between equidistant coordinates in the x and y directions

enter image description here

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; }
    #canvas{border:1px solid red;}
</style>

<script>
$(function(){

    // get references to the canvas and context
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    // style the context
    ctx.strokeStyle = "blue";
    ctx.lineWidth=3;

    // calculate where the canvas is on the window
    // (used to help calculate mouseX/mouseY)
    var $canvas=$("#canvas");
    var canvasOffset=$canvas.offset();
    var offsetX=canvasOffset.left;
    var offsetY=canvasOffset.top;
    var scrollX=$canvas.scrollLeft();
    var scrollY=$canvas.scrollTop();

    // this flage is true when the user is dragging the mouse
    var isDown=false;

    // these vars will hold the starting mouse position
    var startX;
    var startY;


    function handleMouseDown(e){
      e.preventDefault();
      e.stopPropagation();

      // save the starting x/y of the rectangle
      startX=parseInt(e.clientX-offsetX);
      startY=parseInt(e.clientY-offsetY);

      // set a flag indicating the drag has begun
      isDown=true;
    }

    function handleMouseUp(e){
      e.preventDefault();
      e.stopPropagation();

      // the drag is over, clear the dragging flag
      isDown=false;
    }

    function handleMouseOut(e){
      e.preventDefault();
      e.stopPropagation();

      // the drag is over, clear the dragging flag
      isDown=false;
    }

    function handleMouseMove(e){
      e.preventDefault();
      e.stopPropagation();

      // if we're not dragging, just return
      if(!isDown){return;}

      // get the current mouse position
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);

      // Put your mousemove stuff here

      // clear the canvas
      ctx.clearRect(0,0,canvas.width,canvas.height);

      // calculate the rectangle width/height based
      // on starting vs current mouse position
      var width=mouseX-startX;
      var height=mouseY-startY;

      // draw a new rect from the start position 
      // to the current mouse position
      ctx.strokeRect(startX,startY,width,height);

    }

    // listen for mouse events
    $("#canvas").mousedown(function(e){handleMouseDown(e);});
    $("#canvas").mousemove(function(e){handleMouseMove(e);});
    $("#canvas").mouseup(function(e){handleMouseUp(e);});
    $("#canvas").mouseout(function(e){handleMouseOut(e);});

}); // end $(function(){});
</script>
</head>
<body>
    <h4>Drag the mouse to create a rectangle</h4>
    <canvas id="canvas" width=300 height=300></canvas>
</body>
</html>

Upvotes: 4

Views: 1478

Answers (1)

obscure
obscure

Reputation: 12891

What you want to do can be achieved by filling the area depicted by your rectangle using a fill which resembles the red/white rotated pattern. But how do we actually create this pattern?

(1) Create a tile

A tile is basically a shape which can be seamlessly repeated in either direction. In your case something like this should do the job:

which can be created on the fly using a linear gradient:

    let tile = document.createElement('canvas');
    tile.width = tile.height = 10;
    let ctx = tile.getContext('2d');
    let gradient = ctx.createLinearGradient(0, 0, tile.width, tile.height);
    let colorStops = [
        [0, 'white'],
        [0.35, 'white'],
        [0.35, 'red'],
        [0.5, 'red'],
        [0.5, 'white'],
        [0.85, 'white'],
        [0.85, 'red'],
        [1, 'red']
    ];
    colorStops.forEach(element => {
        gradient.addColorStop(element[0], element[1]);
    });

    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, tile.width, tile.height);

If we repeat that tile 10 times both horizontally and vertically we get:

(2) Fill the rectangle using the tile

Actually it's not that hard as you already have the position and the size of the rectangle. So all we have to do is making the tile from step (1) a reusable pattern for the context we want to use it.

let tilePattern=ctx.createPattern(tile, 'repeat');

So everything that's left is a slight modification to your mouseMove handler:

ctx.fillStyle = tilePattern;
ctx.fillRect(startX, startY, width,height);
ctx.strokeRect(startX, startY, width, height);

Now if we put everything together we get:

$(function() {
  let tile = document.createElement('canvas');
  tile.width = tile.height = 10;
  let ctx = tile.getContext('2d');
  let gradient = ctx.createLinearGradient(0, 0, tile.width, tile.height);
  let colorStops = [
    [0, 'white'],
    [0.35, 'white'],
    [0.35, 'red'],
    [0.5, 'red'],
    [0.5, 'white'],
    [0.85, 'white'],
    [0.85, 'red'],
    [1, 'red']
  ];
  colorStops.forEach(element => {
    gradient.addColorStop(element[0], element[1]);
  });

  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, tile.width, tile.height);


  let canvas = document.getElementById("canvas");
  ctx = canvas.getContext("2d");
  let tilePattern = ctx.createPattern(tile, 'repeat');
  ctx.strokeStyle = "blue";
  ctx.lineWidth = 3;

  let $canvas = $("#canvas");
  let canvasOffset = $canvas.offset();
  let offsetX = canvasOffset.left;
  let offsetY = canvasOffset.top;
  let scrollX = $canvas.scrollLeft();
  let scrollY = $canvas.scrollTop();

  let isDown = false;

  let startX, startY, width, height;

  function handleMouseDown(e) {
    e.preventDefault();
    e.stopPropagation();

    startX = parseInt(e.clientX - offsetX);
    startY = parseInt(e.clientY - offsetY);
    width = 0;
    height = 0;
    isDown = true;
  }

  function handleMouseUp(e) {
    e.preventDefault();
    e.stopPropagation();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.strokeRect(startX, startY, width, height);
    isDown = false;
  }

  function handleMouseOut(e) {
    e.preventDefault();
    e.stopPropagation();

    isDown = false;
  }

  function handleMouseMove(e) {
    e.preventDefault();
    e.stopPropagation();

    if (!isDown) {
      return;
    }

    mouseX = parseInt(e.clientX - offsetX);
    mouseY = parseInt(e.clientY - offsetY);

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    width = mouseX - startX;
    height = mouseY - startY;


    ctx.fillStyle = tilePattern;
    ctx.fillRect(startX, startY, width, height);
    ctx.strokeRect(startX, startY, width, height);
  }

  $("#canvas").mousedown(function(e) {
    handleMouseDown(e);
  });
  $("#canvas").mousemove(function(e) {
    handleMouseMove(e);
  });
  $("#canvas").mouseup(function(e) {
    handleMouseUp(e);
  });
  $("#canvas").mouseout(function(e) {
    handleMouseOut(e);
  });

});
body {
  background-color: ivory;
}

#canvas {
  border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h4>Drag the mouse to create a rectangle</h4>
<canvas id="canvas" width=300 height=300></canvas>

Upvotes: 5

Related Questions