Home > OS >  Canvas text click not working. How to fix?
Canvas text click not working. How to fix?

Time:12-15

JS 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";
var print = console.log;

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

    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 myDown(e) {
    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)){
            print("found");
            dragF = i;
        }
    }
}

$("#canvas").mousedown(function(e) { 
    myDown(e);
});

Basically, when you click the text. It's not printing "found" in the console log, nor is assigning it to the dragF variable. I think the issue is something to do with the hitDrag function. I want the hitDrag function to work, so when I click the text it will do what's required to do. (I renamed console.log to print for personal reasons)

function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (var i = 0; i < texts.length; i  ) {
        var text = texts[i];
        ctx.fillText(text.text, text.x, text.y);
    }
    for (var i = 0; i < images.length; i  ) {
        var image = images[i];
        var newImg = new Image;
        newImg.src = image.src;
        newImg.onload = function() {
            ctx.drawImage(this, 20, 20);
        };
    }
}

CodePudding user response:

The key to your issue lies here:

 console.log("t:", texts.length); // empty array
  //empty array so does nothing
  for (var i = 0; i < texts.length; i  ) {

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

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

  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 myDown(e) {
  console.log("called");
  e.preventDefault();
  e.stopPropagation();
  console.log("c2");
  mx = parseInt(e.clientX - offsetX);
  my = parseInt(e.clientY - offsetY);
  console.log("t:", texts.length); // empty array
  //empty array so does nothing
  for (var i = 0; i < texts.length; i  ) {
    console.log("i:", i);
    if (hitDrag(mx, my, i)) {
      print("found");
      dragF = i;
    }
  }
}

$("#canvas").mousedown(function(e) {
  console.log("triggered");
  myDown(e);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>

CodePudding user response:

The problem is that you are using the default textBaseline = "alphabetic", this makes the y value correspond to the bottom of glyphs like o.

You can see that your text BBox is wrong by tracing it:

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";
var print = console.log;

function addNewText(string_text, arrayname) {
    var y = texts.length * 40   40;
    var text = {
        text: string_text,
        x: 20,
        y: y,
        name: arrayname
    };
    ctx.font = "32px verdana";
    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 myDown(e) {
    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)){
            print("found");
            dragF = i;
        }
    }
}

$("#canvas").mousedown(function(e) { 
    myDown(e);
});

function draw() {
  for(const { text, x, y, width, height } of texts) {
    ctx.fillText(text, x, y);
    // trace the text's BBox
    ctx.strokeRect(x, y, width, height);
  }
}

addNewText("Hello world", "text 1");
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>

Setting this textBaseline to "top" would minimize the issue at lower cost, but that won't be a perfect fit, moreover if you use special glyphs.

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";
var print = console.log;

function addNewText(string_text, arrayname) {
    var y = texts.length * 40   40;
    var text = {
        text: string_text,
        x: 20,
        y: y,
        name: arrayname
    };
    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 myDown(e) {
    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)){
            print("found");
            dragF = i;
        }
    }
}

$("#canvas").mousedown(function(e) { 
    myDown(e);
});

function draw() {
  for(const { text, x, y, width, height } of texts) {
    ctx.fillText(text, x, y);
    // trace the text's BBox
    ctx.strokeRect(x, y, width, height);
  }
}

addNewText("Hello world", "text 1");
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>

All major browsers finally support the actualBoundingBoxXXX properties of the TextMetrics interface, so we can now get precise text BBox:

function getTextBBox( ctx, text ) {
  const metrics = ctx.measureText( text );
  const left = metrics.actualBoundingBoxLeft * -1;
  const top = metrics.actualBoundingBoxAscent * -1;
  const right = metrics.actualBoundingBoxRight;
  const bottom = metrics.actualBoundingBoxDescent;
  const width = right - left;
  const height = bottom - top;
  return { left, top, right, bottom, width, height };
}

function getTextBBox(ctx, text) {
  const metrics = ctx.measureText(text);
  const left = metrics.actualBoundingBoxLeft * -1;
  const top = metrics.actualBoundingBoxAscent * -1;
  const right = metrics.actualBoundingBoxRight;
  const bottom = metrics.actualBoundingBoxDescent;
  const width = right - left;
  const height = bottom - top;
  return { left, top, right, bottom, width, height };
}
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";
var print = console.log;

function addNewText(string_text, arrayname) {
    ctx.font = "32px verdana";
    ctx.textBaseline = "top";
    const bbox = getTextBBox(ctx, string_text);
    const prevText = texts[ texts.length - 1 ];
    const text = {
        text: string_text,
        x: 20,
        y: (prevText ? (prevText.y   prevText.bbox.bottom) : 32)   2,
        bbox,
        name: arrayname
    };
    texts.push(text);
    draw();
}

function hitDrag(mx,my,textIndex) {
    const { x, y, bbox: { left, right, top, bottom } } = texts[textIndex];
    return (
      mx > x   left &&
      mx < x   right &&
      my > y   top &&
      my < y   bottom
    );
}

function myDown(e) {
    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)){
            print("found");
            dragF = i;
        }
    }
}

$("#canvas").mousedown(function(e) { 
    myDown(e);
});

function draw() {
  for(const { text, x, y, bbox } of texts) {
    ctx.fillText(text, x, y);
    // trace the text's BBox
    ctx.strokeRect(x   bbox.left, y   bbox.top, bbox.width, bbox.height);
  }
}

addNewText("Hello world", "text 1");
addNewText("Works fine?", "text 2");
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas"></canvas>

Now, if you need to find if you clicked on an actual painted pixel of a glyph, you'd need to get the ImageData of the canvas, I already shown how to do this in an other answer, so I won't repeat it here.

  • Related