Search code examples
javascripthtmlcanvasmouseevent

Draw a rectangle using canvas with a fixed width


I am trying to let the user draw a rectangle on an image that is inside canvas using mousemove event. It works just fine with this code:

HTML:

    <canvas id="canvas"></canvas>

JavaScript:

window.onload = drawCanvas('http://www.therebeldandy.com/wp-content/uploads/2016/09/Menswear-Dog-4.jpg');

var context = canvas.getContext('2d');

function drawCanvas(src) {

    var rect = {};
    var drag = false;
    var img = new Image();
    var canvas = document.getElementById('canvas');

    img.src = src;
    img.addEventListener('load', function () {
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        context.drawImage(img, 0, 0);
    }, false);

    // Draw user selection
    canvas.addEventListener('mousedown', mouseDown, false);
    canvas.addEventListener('mouseup', mouseUp, false);
    canvas.addEventListener('mousemove', mouseMove, false);

    function mouseDown(e) {
        rect.startX = e.pageX - this.offsetLeft;
        rect.startY = e.pageY - this.offsetTop;
        drag = true;
    }

    function mouseUp() {
        drag = false;
    }

    function mouseMove(e) {
        if (drag) {
            context.clearRect(0, 0, 500, 500);
            context.drawImage(img, 0, 0);
            rect.w = (e.pageX - this.offsetLeft) - rect.startX;
            rect.h = (e.pageY - this.offsetTop) - rect.startY;
            context.lineWidth = 3;
            context.strokeStyle = '#df4b26';
            context.fillStyle = "rgba(255, 255, 255, .25)";
            context.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
            context.fillRect(rect.startX, rect.startY, rect.w, rect.h);
            console.log(rect.startX, rect.startY, rect.w, rect.h);
        }
    }

}

The problem is I need to make sure the image has a fixed width, so I use this HTML instead:

<canvas id="canvas" style="max-width: 300px; height: auto;"></canvas>

Just adding this styling breaks the entire thing: I'm not able to select the part of the image I want anymore.

Here's a fiddle that will give you a better understanding of what I'm trying to accomplish: JSFiddle

Is there a way to make this work? I've been struggling for hours so any help would be greatly appreciated.


Solution

  • Unfortunately, as you discovered, CSS sizing mangles canvas drawings. If I understand the result you're after, you want variable height and fixed width with the source image scaled to fit. This code, adapted from another answer, should do that:

      var width = 500; // specify your fixed width here
      var h;
      var w;
      var scale;
    
      img.addEventListener('load', function() {
        h = img.naturalHeight;
        w = img.naturalWidth;
        scale = Math.min(width / w, width / h);
        canvas.width = width;
        canvas.height = h * scale;
        context.drawImage(img, 0, 0, w, h, 0, 0, w * scale, h * scale);
      }, false);
      img.src = src;
    

    Note that I also changed a second contex.drawImage() call to match the above one in your code. Here's the updated code:

    window.onload = drawCanvas('https://i.ndtvimg.com/i/2016-10/spud-boxer-best-dressed-dog_650x400_41476182056.jpg');
    
    var context = canvas.getContext('2d');
    
    function drawCanvas(src) {
      var rect = {};
      var drag = false;
      var img = new Image();
      var canvas = document.getElementById('canvas');
    
      img.addEventListener('load', function () {
        canvas.width = img.naturalWidth;
        canvas.height = img.naturalHeight;
        context.drawImage(img, 0, 0);
      }, false);
      img.src = src;
    
      // Draw user selection if image is loaded
      canvas.addEventListener('mousedown', mouseDown, false);
      canvas.addEventListener('mouseup', mouseUp, false);
      canvas.addEventListener('mousemove', mouseMove, false);
    
      function mouseDown(e) {
        rect.startX = e.pageX - this.offsetLeft;
        rect.startY = e.pageY - this.offsetTop;
        drag = true;
      }
    
      function mouseUp() {
        drag = false;
      }
    
      function mouseMove(e) {
        if (drag) {
          context.clearRect(0, 0, 500, 500);
          context.drawImage(img, 0, 0);
          rect.w = (e.pageX - this.offsetLeft) - rect.startX;
          rect.h = (e.pageY - this.offsetTop) - rect.startY;
          context.lineWidth = 3;
          context.strokeStyle = '#df4b26';
          context.fillStyle = "rgba(255, 255, 255, .25)";
          context.strokeRect(rect.startX, rect.startY, rect.w, rect.h);
          context.fillRect(rect.startX, rect.startY, rect.w, rect.h);
          console.log(rect.startX, rect.startY, rect.w, rect.h);
        }
      }
    }
    
    document.getElementById("button").onclick = function() {
      changeImg();
    };
    
    function changeImg() {
      //context.clearRect(0, 0, canvas.width, canvas.height);
      drawCanvas("http://www.therebeldandy.com/wp-content/uploads/2016/09/Menswear-Dog-4.jpg");
    }
    <canvas id="canvas" style="max-width: 300px; height: auto;"></canvas>
    <br />
    <button id="button">ChangeImage</button>