0

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";

function print(log) {
    console.log(log)
}

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

function addNewText(string_text, arrayname) {
    var y = texts.length * 20 + 20;
    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);
});

addNewText("Hello world", "text 1");

So I followed @Kaiido advice with the BBox and I changed textBaseline to "top". Which fits perfectly. Now when I run his code snipper under my help forum, it seems to work and print "found" (which shows it works). When I run it, it doesn't seem to work. What could be the reason for this.

His Code working: https://gyazo.com/511bf35523fcb3ea8a26c2b088530f99

My coding not working: https://gyazo.com/743c38f6a33a7f1f4513bac361c23588

HTML Code -

<div id="middle_container">
            <div class="center_container">
                <canvas id="canvas"></canvas>
            </div>
        </div>

CSS Code -

  #canvas {
        height: 667px;
        width: 800px;
        touch-action: auto;
        cursor: inherit;
        visibility: visible;
        border: 1px solid grey;
    }
billy
  • 37
  • 1
  • 8
  • I would suggest `var print = console.log;` instead be something like `var myprint = function(mything){console.log(mything);};` and not redefine `window.print()` – Mark Schultheiss Dec 13 '21 at 22:13
  • Please add the HTML for your `$("#canvas")` you are using – Mark Schultheiss Dec 13 '21 at 22:22
  • Hello, first of all, thanks for helping! The array isn’t empty because the addNewText function adds an object into the array. So assuming the object has been added via the addNewText function, it’s still not detecting the click. – billy Dec 13 '21 at 23:23
  • Call that function - `addNewText("charlie""delta"); ` and it is 0 length. Perhaps update your question to be a more complete example? – Mark Schultheiss Dec 13 '21 at 23:27
  • We need to see `draw`. From the look of it, you don't set the `textBaseline` of your context, so the y axis of your BBox is off by one line-height. If you want a more bullet proof function to detect click on text in a canvas: https://stackoverflow.com/a/67015797/3702797 – Kaiido Dec 14 '21 at 00:59
  • @MarkSchultheiss I added the "console.log("t:", texts.length);" and it printed "t: 1" every time I added another text, it printed another e.g. "t: 2". I added the draw function, can you please check to see if the issue is something else. – billy Dec 14 '21 at 02:19

2 Answers2

0

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>
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
  • They probably call `addNewText` which will populate this array and draw the text on the context, otherwise I suppose their question would have been, "why is there no text being drawn?", instead of "why nothing happens when I click on the drawn text?" – Kaiido Dec 14 '21 at 00:56
  • I updated and added the draw function to the main forum. When I click the drawn text, I just want it to print something in console log so I know it works. – billy Dec 14 '21 at 02:55
  • Hello, first of all thanks for helping @Kaiido | The issue seems to fixed for your code snippet when I tested it but it still doesn't work for me. Can you review it, and check what may be a possible reason for this. I included links to GIFs! – billy Dec 14 '21 at 23:07
0

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.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Hello, first of all thanks for helping @Kaiido | The issue seems to fixed for your code snippet when I tested it but it still doesn't work for me. Can you review it, and check what may be a possible reason for this. I included links to GIFs! (Sorry for the repeated comment, I don't know where to comment since I'm new to this) – billy Dec 14 '21 at 23:16
  • You are now facing https://stackoverflow.com/questions/2588181/canvas-is-stretched-when-using-css-but-normal-with-width-height-properties and commenting about my answer is best under my answer. – Kaiido Dec 14 '21 at 23:26
  • Thank you so much! You have helped a lot. Have a great day, mate. – billy Dec 15 '21 at 05:04