jjr2000
jjr2000

Reputation: 557

Canvas point plotting doesn't scale after resize

After Dynamically resizing a canvas using javascript points plotted on it no longer line up with where they are placed, and also arcs are getting stretched from how they should be rendered.

The below runnable example demonstrates the issue, because what should be a point where you click the cursor turns into a large miss-shapen oval which is in the wrong place.

var TargetWidth = 400;

var canvases = $(".hotspot-canvas")

for (i = 0; i < canvases.length; i++) { 
    canvas = $(canvases[i]);

    var src = canvas.attr("data-img");

    initilizePlotPointCanvas(canvas, src);
  
}

function initilizePlotPointCanvas(canvas, src)
{
  var my_image = new Image();
  my_image.onload = function(){
    
    var w1 = this.width;
    var w2 = TargetWidth
  
    var r = w1 / w2;
  
    var h1 = this.height;
    var h2 = this.height / r;
  
    canvas.width(TargetWidth).height(h2).css("background-image", "url("+src+")");
    setTimeout(function(){
      
      var jcanvas = canvas[0];
    
    var ctx = jcanvas.getContext('2d'),
    w = jcanvas.width,
    h = jcanvas.height;
    ctx.translate(-0.1, -0.1);
    jcanvas.onmousedown = function(e) {
      var rect = jcanvas.getBoundingClientRect();
      x3 = e.clientX - rect.left;
      y3 = e.clientY - rect.top;
  
      ctx.clearRect(0, 0, w, h)
      ctx.beginPath();
      ctx.arc(x3, y3, 5, 0, 2 * Math.PI, false);
      ctx.fillStyle = 'red';
      ctx.fill();
      ctx.stroke();
    }
      
    }, 500)
    
  }
  my_image.src = src;
}
.hotspot-canvas {
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
    cursor: crosshair;
    border: 1px solid black;
    background-repeat: no-repeat;
    background-position: center center;
    background-clip: border-box;
    background-origin: padding-box;
    -moz-background-size: cover;
    background-size: cover;
}
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<canvas 
  data-id="554924"
  data-img="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQXXarydhE3CZRSMgXCProd1w0_oSwIOPd7zJN5EQmLtQtPDD21" 
  class="hotspot-canvas"
   data-responses="0" data-notes="0" data-actions="0" data-email="123" data-responder="true" data-response-id="" orig_type="14" data-tag="6bbf8f97-758f-47ca-8b8c-24a1cd3ddd55" data-formtemplatequestiontype="s" data-valueifparentna="" data-exportkey=""></canvas>

Upvotes: 4

Views: 1039

Answers (2)

kamoroso94
kamoroso94

Reputation: 1735

Like I mentioned in my comment, I've run into this same problem quite recently, so I'd like to share my solution. Basically what's happening is your canvas element's dimensions are scaled and its origin is translated due to CSS, but the internal resolution of the canvas is still the same. Also, the border and padding of your canvas also affect the DOMRect's properties returned by getBoundingClientRect.

You need to take the clientX and clientY values from some MouseEvent or TouchEvent, the internal resolution of your canvas, and the dimensions and offset of the displayed canvas element, including border and padding, and use them to calculate the xy-coordinate relating to the canvas' internal coordinate system. This code requires JQuery and ES6 support, but you can compile down with Babel or something if you're worried about browser compatibility.

// translate coords in viewport to internal coords of canvas
const translateCoords = ({clientX, clientY}) => {
    // dimensions of canvas element (top-left with respect to border-box)
    const bcr = canvas.getBoundingClientRect();
    // offsets from border and padding
    const sideWidth = ["top", "right", "bottom", "left"].reduce((sum, side) => {
        sum[side] = Object.values($(canvas).css([
            `border-${side}-width`,
            `padding-${side}`
        ])).reduce((a, b) => a + parseFloat(b), 0);
        return sum;
    }, {});
    // ratio of internal canvas resolution to canvas displayed dimensions
    const scaleX = canvas.width / (bcr.width - (sideWidth.left + sideWidth.right));
    const scaleY = canvas.height / (bcr.height - (sideWidth.top + sideWidth.bottom));
    // translate and scale screen coords to canvas internal coords
    const x = (clientX - (bcr.left + sideWidth.left)) * scaleX;
    const y = (clientY - (bcr.top + sideWidth.top)) * scaleY;

    return {x, y};
};
// example event listener
canvas.addEventListener("click", (event) => {
    const {x, y} = translateCoords(event);
    // do something with translated coordinates
    console.log(x, y);
});

Upvotes: 0

Blindman67
Blindman67

Reputation: 54026

Display Size and canvas resolution

Display size and canvas resolution are two different entities.

When you set the canvas style width and height you set the display size

canvas.style.width = "100px";
canvas.style.height = "100px";

When you set the canvas width and height you set the resolution.

canvas.width = 100;
canvas.height = 100;

You have set the display size but neglected to match the resolution to the display size.

You can fix it by just setting the resolution to match the display size

var bounds = jcanvas.getBoundingClientRect();
jcanvas.width = bounds.width;
jcanvas.height = bounds.height; 

var TargetWidth = 400;

var canvases = $(".hotspot-canvas")

for (i = 0; i < canvases.length; i++) { 
    canvas = $(canvases[i]);

    var src = canvas.attr("data-img");

    initilizePlotPointCanvas(canvas, src);
  
}

function initilizePlotPointCanvas(canvas, src)
{
  var my_image = new Image();
  my_image.onload = function(){
    
    var w1 = this.width;
    var w2 = TargetWidth
  
    var r = w1 / w2;
  
    var h1 = this.height;
    var h2 = this.height / r;
  
    canvas.width(TargetWidth).height(h2).css("background-image", "url("+src+")");

    setTimeout(function(){
      
      var jcanvas = canvas[0];
     var bounds = jcanvas.getBoundingClientRect();
    jcanvas.width = bounds.width;
    jcanvas.height = bounds.height;        
    var ctx = jcanvas.getContext('2d'),
    w = jcanvas.width,
    h = jcanvas.height;
    ctx.translate(-0.1, -0.1);
    jcanvas.onmousedown = function(e) {
      var rect = jcanvas.getBoundingClientRect();
      x3 = e.clientX - rect.left;
      y3 = e.clientY - rect.top;
  
      ctx.clearRect(0, 0, w, h)
      ctx.beginPath();
      ctx.arc(x3, y3, 5, 0, 2 * Math.PI, false);
      ctx.fillStyle = 'red';
      ctx.fill();
      ctx.stroke();
    }
      
    }, 500)
    
  }
  my_image.src = src;
}
.hotspot-canvas {
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
    cursor: crosshair;
    border: 1px solid black;
    background-repeat: no-repeat;
    background-position: center center;
    background-clip: border-box;
    background-origin: padding-box;
    -moz-background-size: cover;
    background-size: cover;
}
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

<canvas 
  data-id="554924"
  data-img="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQXXarydhE3CZRSMgXCProd1w0_oSwIOPd7zJN5EQmLtQtPDD21" 
  class="hotspot-canvas"
   data-responses="0" data-notes="0" data-actions="0" data-email="123" data-responder="true" data-response-id="" orig_type="14" data-tag="6bbf8f97-758f-47ca-8b8c-24a1cd3ddd55" data-formtemplatequestiontype="s" data-valueifparentna="" data-exportkey=""></canvas>

Upvotes: 3

Related Questions