Search code examples
javascripthtml5-canvas

Issue when dragging text on canvas if slightly scrolled down


When I scroll slightly down on my canvas, it does not allow me to drag my text at all. Examples (These are GIFs) -

https://gyazo.com/e60d2efd924ced758c2c6441391804db

GIF explained: So you saw the drag was working when I was on top of the page but when I scrolled slightly down. It completely stopped working. I added a few console.logs around, and what I know is the click event listener for the canvas is working but it isn't detected the text when I slightly scroll down on the page.

I based my drag code from: http://jsfiddle.net/m1erickson/9xAGa/ | What you can see is if you change the canvas size width: 667 and height: 800, and when you scroll slightly down, you will have the same issue I am having.

HTML Code:

<div id="middle_container">
        <div class="center_container">
            <canvas id="canvas" width="667px" height="800px"></canvas>
        </div>
</div>

JavaScript Code:

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var BB=canvas.getBoundingClientRect();
var offsetX = BB.left;
var offsetY = BB.top;
var mx;
var my;
var texts = [];
var images = [];
var dragF = -1;
var mode = "none";

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for(const { text, x, y, width, height } of texts) {
        ctx.fillText(text, x, y);
    }
}

function addNewText(string_text) {
    var y = texts.length * 20 + 20;
    var text = {
        text: string_text,
        x: 20,
        y: y
    };
    ctx.font = "32px verdana";
    ctx.textBaseline = "top";
    text.width = ctx.measureText(text.text).width;
    text.height = 32;

    texts.push(text);
    draw();
}



function hitDrag(x,y,textIndex) {
    var r=texts[textIndex];
    return (x>r.x && x<r.x+r.width && y>r.y && y<r.y+r.height);
}

function myDrag(a,e) {
    if (a == "down") {
        e.preventDefault();
        e.stopPropagation();
        mx=parseInt(e.clientX-offsetX);
        my=parseInt(e.clientY-offsetY);
        for(var i=0;i<texts.length;i++){
            if(hitDrag(mx,my,i)){
                console.log("found");
                dragF = i;
            }
        }
    }
}

addNewText("Hello World")

$("#canvas").mousedown(function(e) {
    myDrag("down", e);
});

Solution

  • The problem is this line of code:

    var BB=canvas.getBoundingClientRect();
    

    which is only populated once at the start of your script. The .getBoundingClientRect() method returns the position of a HTML element relative to the viewport.

    Well if you scroll the window, the viewport moves - as the canvas element - but the BB object still holds the position of your canvas at startup.

    The fix is rather simple - you need to use the actual position of the canvas element by calling .getBoundingClientRect() again on mouse down and on mouse move.

    I've prepared a little sample based on your code and the fiddle you've linked:

    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    var $canvas = $("#canvas");
    var BB = canvas.getBoundingClientRect();
    var offsetX = BB.left;
    var offsetY = BB.top;
    var mx;
    var my;
    var texts = [];
    var images = [];
    var dragF = -1;
    var mode = "none";
    
    function draw() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      for (const {
          text,
          x,
          y,
          width,
          height
        } of texts) {
        ctx.fillText(text, x, y);
      }
    }
    
    function addNewText(string_text) {
      var y = texts.length * 20 + 20;
      var text = {
        text: string_text,
        x: 20,
        y: y
      };
      ctx.font = "32px verdana";
      ctx.textBaseline = "top";
      text.width = ctx.measureText(text.text).width;
      text.height = 32;
    
      texts.push(text);
      draw();
    }
    
    function myDrag(a, e) {
      if (a == "down") {
        e.preventDefault();
        e.stopPropagation();
        let rect = canvas.getBoundingClientRect();
        mx = parseInt(e.clientX - rect.left);
        my = parseInt(e.clientY - rect.top);
        for (var i = 0; i < texts.length; i++) {
          if (hitDrag(mx, my, i)) {
            // console.log("found");
            dragF = i;
          }
        }
      }
    }
    
    function hitDrag(x, y, textIndex) {
      var r = texts[textIndex];
      return (x > r.x && x < r.x + r.width && y > r.y && y < r.y + r.height);
    }
    
    function handleMouseMove(e) {
      if (dragF < 0) {
        return;
      }
      e.preventDefault();
      let rect = canvas.getBoundingClientRect();
      mouseX = parseInt(e.clientX - rect.left);
      mouseY = parseInt(e.clientY - rect.top);
    
      var dx = mouseX - mx;
      var dy = mouseY - my;
      mx = mouseX;
      my = mouseY;
    
      var text = texts[dragF];
      text.x += dx;
      text.y += dy;
      draw();
    }
    
    function handleMouseUp(e) {
      e.preventDefault();
      dragF = -1;
    }
    
    addNewText("Hello World")
    
    $("#canvas").mousedown(function(e) {
      myDrag("down", e);
    });
    
    $("#canvas").mousemove(function(e) {
      handleMouseMove(e);
    });
    
    $("#canvas").mouseup(function(e) {
      handleMouseUp(e);
    });
    body {
      background-color: ivory;
    }
    
    #canvas {
      border: 1px solid red;
    }
    
    #theText {
      width: 10em;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="middle_container">
      <div class="center_container">
        <canvas id="canvas" width="667px" height="800px"></canvas>
      </div>
    </div>
    <br><br><br><br><br><br><br><br><br><br>
    <br><br><br><br><br><br><br><br><br><br>