Changing the letter by pressing a key - javascript

I have a canvas with some styling applied. There is a single letter centered in the canvas. Please take a look at the code below.
As the title suggest, I am trying to change the letter by pressing a key.
For example:
Letter A centered in canvas: I press the g - key, it changes to the letter g (Uppercase included)
As to my knowledge, I might have to use the method "keyup" with a "document.addEventListener".
Currently I am going through a course on learning JS, but I have noticed a strong reliance on certain libraries in the course, which I frankly dislike. I am not trashing the benefits, but I would prefer building a base with pure JS before using certain libraries I hardly understand. Some guidance would be appreciated.
body {
background-color: #000000;
}
canvas {
padding: 0;
margin: auto;
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: #111416;
border: 10px solid #a60000;
border-style: double;
box-shadow: 0 0 20px 5px #a60000;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<link rel="stylesheet" href="canvas.css">
<canvas id="myCanvas" width="800" height="800"></canvas>
<script>
// Get id from the canvas element
var canvas = document.getElementById("myCanvas");
// Provide 2D rendering context for the drawing surface of canvas
var context = canvas.getContext("2d");
// Get width and height of the canvas element
var canvW = document.getElementById("myCanvas").width;
var canvH = document.getElementById("myCanvas").height;
let text = "f";
context.fillStyle = "#a60000";
context.font = "700px serif";
// Measure the size of the letter and the specific font
// Always centers the letter regardless of size
// Display size of letter
const metrics = context.measureText(text);
const mx = metrics.actualBoundingBoxLeft * -1;
const my = metrics.actualBoundingBoxAscent * -1;
const mw = metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight;
const mh = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
const x = (canvW -mw) *0.5 - mx;
const y = (canvH - mh) *0.5 - my;
context.save();
context.translate(x, y);
context.beginPath();
context.rect(mx, my, mw, mh);
context.stroke();
context.fillText(text, 0, 0);
context.restore();
const onKeyUp = (e) => {
text = e.key.toUpperCase();
manager.render();
};
document.addEventListener("keyup", onKeyUp);
</script>
</body>
</html>

Your function call manager.render() results in an error as it's not present in the provided code snippet. I created one and moved your drawing stuff into it. The render function takes the input as an argument. Besides that I just had to add clearRect() to prevent overlapping letters.
// Get id from the canvas element
var canvas = document.getElementById("myCanvas");
// Provide 2D rendering context for the drawing surface of canvas
var context = canvas.getContext("2d");
// Get width and height of the canvas element
var canvW = document.getElementById("myCanvas").width;
var canvH = document.getElementById("myCanvas").height;
context.fillStyle = "#a60000";
context.font = "700px serif";
render("t");
function render(text) {
// Measure the size of the letter and the specific font
// Always centers the letter regardless of size
// Display size of letter
const metrics = context.measureText(text);
const mx = metrics.actualBoundingBoxLeft * -1;
const my = metrics.actualBoundingBoxAscent * -1;
const mw = metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight;
const mh = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
const x = (canvW -mw) *0.5 - mx;
const y = (canvH - mh) *0.5 - my;
context.clearRect(0,0,canvas.width,canvas.height)
context.save();
context.translate(x, y);
context.beginPath();
context.rect(mx, my, mw, mh);
context.stroke();
context.fillText(text, 0, 0);
context.restore();
}
const onKeyUp = (e) => {
const text = e.key.toUpperCase();
render(text);
};
document.addEventListener("keyup", onKeyUp);
body {
background-color: #000000;
}
canvas {
padding: 0;
margin: auto;
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: #111416;
border: 10px solid #a60000;
border-style: double;
box-shadow: 0 0 20px 5px #a60000;
}
<canvas id="myCanvas" width="800" height="800"></canvas>

The problem is that you dont redraw the canvas again after the hitting the button. For that you have to call the method context.clearRect(0, 0, canvas.width, canvas.height);In the eventlistener you have to call a function to redraw by wrapping your logic into a function. Now you can pass to the function the text as parameter you want to have rendered. Working Example:
code:
const redraw = function (text) {
var canvas = document.getElementById('myCanvas');
// Provide 2D rendering context for the drawing surface of canvas
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// Get width and height of the canvas element
var canvW = document.getElementById('myCanvas').width;
var canvH = document.getElementById('myCanvas').height;
context.fillStyle = '#a60000';
context.font = '700px serif';
// Measure the size of the letter and the specific font
// Always centers the letter regardless of size
// Display size of letter
const metrics = context.measureText(text);
const mx = metrics.actualBoundingBoxLeft * -1;
const my = metrics.actualBoundingBoxAscent * -1;
const mw = metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight;
const mh = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
const x = (canvW - mw) * 0.5 - mx;
const y = (canvH - mh) * 0.5 - my;
context.save();
context.translate(x, y);
context.beginPath();
context.rect(mx, my, mw, mh);
context.stroke();
context.fillText(text, 0, 0);
context.restore();
};
redraw('I');
const onKeyUp = (e) => {
text = e.key.toUpperCase();
redraw(text);
};
document.addEventListener('keyup', onKeyUp);

Related

Scrolling issue with Canvas style.left/top

I am making a magnifying glass for a canvas application but ive run into an issue with scrolling.
Essentially the goal is to take a canvas and when an image is added (in this case a cgm image) and make a snapshot of it, scale it up and draw it on to a second smaller canvas that overlays it and follow the mouse (preferably a drag/mousedown and up, but i am using mousemove for testing purposes). I believe the issue is in the zoom.style.top & zoom.style.left NOTE on my main aplication I have a top margin of 70px so keep that in mind.
here is a quick example I wrote up
<!-- language: lang-js -->
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
var ox = canvas.width / 2;
var oy = canvas.height / 2;
ctx.font = "42px serif";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "blue";
ctx.fillRect(ox / 2, oy / 2, ox, oy);
function magnify() {
var main = document.getElementById("canvas1");
var ctx = main.getContext('2d')
var base64 = main.toDataURL('image/png', 0);
drawing = new Image();
drawing.onload = () => {
var zoom = document.getElementById("tCanvas");
var zoomCtx = zoom.getContext('2d');
zoomCtx.drawImage(drawing, 0, 0);
}
main.addEventListener("mousemove", function(e) {
var zoom = document.getElementById("tCanvas");
var zoomCtx = zoom.getContext('2d');
zoomCtx.clearRect(0, 0, zoom.width, zoom.height);
zoomCtx.drawImage(main, e.x, e.y, 200, 200, 0, 0, 300, 300);
zoom.style.top = e.pageY - 70 + "px"
zoom.style.left = e.pageX - 10 + "px"
e.pageY = -150
e.pageX = -150
zoom.style.display = "block";
});
main.addEventListener("mouseleave", function() {
var zoom = document.getElementById("tCanvas");
zoom.style.display = "none";
});
drawing.src = base64;
};
<canvas id="tCanvas" class="cgm" height="100" width="100" style="background-color:white; position: absolute; display: none; z-
index:1;border:1px solid red;"> </canvas>
<canvas tabindex=1 class="cgm" id="canvas1" style="position:relative; background:white;
left:0;right:0;margin:auto;z-index:1;margin-top:70px; "></canvas>
<p></p>
<button id="zoom" onclick="magnify();">Zoom</button>
Here's a fiddle for reference (I fixed the height to display the scroll issue).
JSFiddle
I simplified a lot of the code on your JSFiddle.
See if this is the behavior you are looking for:
const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext("2d");
const zoom = document.getElementById("tCanvas");
const zoomCtx = zoom.getContext('2d');
for (let i = 10; i < 20; i++)
for (let j = -60; j < 800; j += 60)
ctx.fillText(j + i, i * 20 - 180, i * 9 + j);
function getMousePos(evt) {
var rect = canvas.getBoundingClientRect()
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
}
}
canvas.addEventListener("mousemove", function(e) {
zoomCtx.clearRect(0, 0, zoom.width, zoom.height);
let pos = getMousePos(e)
zoomCtx.drawImage(canvas, pos.x, pos.y, 200, 200, 0, 0, 300, 300);
zoom.style.top = e.pageY - 10 + "px"
zoom.style.left = e.pageX - 10 + "px"
zoom.style.display = "block";
});
canvas.addEventListener("mouseleave", function() {
zoom.style.display = "none";
});
#tCanvas {
border: 1px solid red;
display: none;
position: absolute;
background-color: #808080;
pointer-events: none;
}
#canvas1 {
background-color: #808080;
}
<canvas id="tCanvas" width=100 height=100></canvas>
<canvas id="canvas1" width=240 height=800></canvas>
I think that your issue was with pointer-events: none; the top canvas was preventing the events from reaching the bottom canvas.
Also you don't have to declare your document.getElementById(".. on every function, those should be global constants
For the issue with a long canvas we need to use canvas.getBoundingClientRect in the calculation to get the real position of the mouse in the canvas

How do you draw a line from the last position of the mouse to the present position of the mouse?

So, I'm creating some drawing maker thing, but when I move the mouse it doesn't draw circles in a line. I don't know how to say it well, but I want it to draw a line from point A (last position of mouse) to point B (present position of mouse).
Start drawing here and you will understand:
const c = document.getElementById('c')
const ctx = c.getContext('2d')
c.height = window.innerHeight
c.width = window.innerWidth
function pen(e, colorHEX) {
let mouseX = e.clientX
let mouseY = e.clientY
ctx.fillStyle = colorHEX
ctx.beginPath()
ctx.arc(mouseX, mouseY, 2, 0, Math.PI * 2)
ctx.fill()
ctx.closePath()
}
$('body').mousedown(function(eObj){
$('body').mousemove(function(e) {
pen(e, '#000000')
})
})
$('body').mouseup(function(eObj) {
$('body').unbind('mousemove')
})
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="style.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<canvas id="c"></canvas>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="script.js"></script>
</body>
</html>
I think you're trying to draw a contiguous line (you don't need to do that by painting circles). You'll need to store the last mouse position, and then draw a line from there to the current mouse position.
const c = document.getElementById('c')
const ctx = c.getContext('2d')
c.height = window.innerHeight
c.width = window.innerWidth
let lastPos // state variable
function pen(e, colorHEX) {
const pos = {x:e.clientX, y: e.clientY}
lastPos = (lastPos || pos) // Default to current pos
ctx.beginPath()
ctx.strokeStyle = colorHEX
ctx.lineWidth = 1
ctx.moveTo(lastPos.x, lastPos.y)
ctx.lineTo(pos.x, pos.y)
ctx.stroke()
ctx.closePath()
// Update the state variable
lastPos = pos
}
$('body').mousedown(function(eObj){
$('body').mousemove(function(e) {
pen(e, '#000000')
})
})
$('body').mouseup(function(eObj) {
$('body').unbind('mousemove')
lastPos = undefined
})
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="style.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<canvas id="c"></canvas>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="script.js"></script>
</body>
</html>
You can use the lineTo method
To make it move while you are dragging the line will take an extra script that includes angles and math.
const c = document.getElementById('c')
const ctx = c.getContext('2d')
c.height = window.innerHeight
c.width = window.innerWidth
let startX;
let startY;
function pen(e, colorHEX) {
let endX = e.clientX
let endY = e.clientY
ctx.fillStyle = colorHEX
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(endX, endY)
ctx.fill()
ctx.closePath()
ctx.stroke()
}
$('body').mousedown(function(e){
startX = e.clientX;
startY = e.clientY;
})
$('body').mouseup(function(e) {
pen(e, "#000000");
})
body {
margin: 0;
overflow: hidden;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="style.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<canvas id="c"></canvas>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="script.js"></script>
</body>
</html>
Drawing with mouse
You have two options.
Draw a line segment from A to B setting the line width to match the circles diameter and the line cap to round.
Interpolate the points from A to B drawing a circle (or any shape) at points 1px apart.
I said two but there are a zillion ways to do this type of thing, but that would put this answer in the TLDR category.
Which method you use will depend on the finer details of your needs. The two example below drawing using the most basic solid line look identical.
What I think you may be having a problem with is because you are turning the event listeners on and off, the last mouse position you have is the last mouse up.
I have added two examples. One for each of the drawing options, and the top one uses an always on mouse listeners and a second turns on and of the mouse listeners as needed.
Examples
The mouse has its state put into an object called mouse.
Each time the mouse changes position the last position is put into mouse.lastX, mouse.lastY.
If the mouse event.type is mousedown the mouse.button is set to true and to false if mouseup
Mouse up and down are also used to turn on and off things like cursor, add and remove events (second example)
At the bottom of the listener it checks if the mouse is down (mouse.button is true) and draws a line from the last position to the current position.
Using line segments
Draws by using 2D API's stroke function.
Use always on mouse listeners.
const ctx = c.getContext('2d');
const BORDER = 1; // I added a 1px border to canvas.
c.height = innerHeight - BORDER * 2;
c.width = innerWidth - BORDER * 2;
const mouse = {x: 0, y: 0, lastX: 0, lastY: 0, button: false};
function drawLine(xA, yA, xB, yB, col, width) {
ctx.strokeStyle = col;
ctx.lineCap = "round";
ctx.lineWidth = width;
ctx.beginPath()
ctx.lineTo(xA, yA);
ctx.lineTo(xB, yB);
ctx.stroke();
}
function mouseEvent(e) {
if (e.type === "mousedown") {
mouse.button = true;
c.classList.add("hideMouse");
} else if (e.type === "mouseup") {
mouse.button = false;
c.classList.remove("hideMouse");
}
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
mouse.x = e.clientX - BORDER;
mouse.y = e.clientY - BORDER;
if (mouse.button) {
drawLine(mouse.lastX, mouse.lastY, mouse.x, mouse.y, "#000", 4);
}
}
addEventListener("mousedown", mouseEvent);
addEventListener("mouseup", mouseEvent);
addEventListener("mousemove", mouseEvent);
body {
margin: 0;
overflow: hidden;
font-family: arial;
}
canvas {
border: 1px solid black;
cursor: crosshair;
}
.hideMouse {
cursor: none;
}
div {
background: #FFF8;
position: absolute;
top: 4px;
left: 4px;
}
<canvas id="c"></canvas>
<div>Click and drag mouse to draw</div>
Interpolate line
Replaces drawLine function body with interpolation algorithum.
Adds mouse listeners mousemove and mouseup, on mousedown. Note that the up listener has the option {once:true} when added so does not need to be manually removed.
const ctx = c.getContext('2d');
const BORDER = 1; // I added a 1px border to canvas.
c.height = innerHeight - BORDER * 2;
c.width = innerWidth - BORDER * 2;
const mouse = {x: 0, y: 0, lastX: 0, lastY: 0, button: false};
function drawLine(xA, yA, xB, yB, col, width) {
var i = 0;
const dx = xB - xA;
const dy = yB - yA;
const length = Math.hypot(dx, dy);
const stepX = length > 0 ? dx / length : 0;
const stepY = length > 0 ? dy / length : 0;
ctx.beginPath();
ctx.fillStyle = col;
while (i <= length) {
ctx.moveTo(xA + width / 2, yA);
ctx.arc(xA, yA, width / 2, 0, Math.PI * 2);
xA += stepX;
yA += stepY;
i ++;
}
ctx.fill();
}
function mouseEvent(e) {
if (e.type === "mousedown") {
mouse.button = true;
c.classList.add("hideMouse");
addEventListener("mouseup", mouseEvent, {once: true});
addEventListener("mousemove", mouseEvent);
// Set mouse pos so last pos is not where the last mouse up was
mouse.x = e.clientX - BORDER;
mouse.y = e.clientY - BORDER;
} else if (e.type === "mouseup") {
mouse.button = false;
c.classList.remove("hideMouse");
removeEventListener("mousemove", mouseEvent);
}
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
mouse.x = e.clientX - BORDER;
mouse.y = e.clientY - BORDER;
if (mouse.button) {
drawLine(mouse.lastX, mouse.lastY, mouse.x, mouse.y, "#000", 4);
}
}
addEventListener("mousedown", mouseEvent);
body {
margin: 0;
overflow: hidden;
font-family: arial;
}
canvas {
border: 1px solid black;
cursor: crosshair;
}
.hideMouse {
cursor: none;
}
div {
background: #FFF8;
position: absolute;
top: 4px;
left: 4px;
}
<canvas id="c"></canvas>
<div>Click and drag mouse to draw</div>
Side note...
jQuery is dead
BTW jQuery is not needed, it became irrelevant about 5 years ago.
Using it puts you many years behind modern front end development.
In the past 5 years the demand for jQuery experience coders has dropped a significant amount. A trend that will continue as jQuery has painted its self into a corner from which the only way out is via re branding and re targeting its purpose.
Every time you use jQuery you are investing your most valuable asset, time. Time spent learning and gaining experience. Be wise and direct that time learning what is applicable and growing in the future, and not into what started dying over a decade ago.
For more details Should you use or learn jQuery in 2020?

HTML Canvas Trying to create an animated chain of rectangle with slight delay/distance between them

I am trying to create multiple animated rectangles using Html Canvas with requestAnimationFrame. As for now, I managed to do exactly what I wanted with only one animated rectangle, but I can't find out how to create more rectangles that would simply be in line and follow each other with an equal distance.
Also, there's a random data (I, II or III) inside each rectangle.
Here's my actual code:
//Referencing canvas
var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
//Make Canvas fullscreen and responsive
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize, false); resize();
//FPS
var framesPerSecond = 60;
//Default Y pos to center;
var yPos = canvas.height / 2;
//Default X pos offset
var xPos = -150;
//Speed (increment)
var speed = 2;
//Our array to store rectangles objects
var rectangles = [] ;
//Dynamic Number from database
var quote = ["I", "II", "III"];
//Random number for testing purpose
var rand = quote[Math.floor(Math.random() * quote.length)];
//Draw Rectangle
function drawRectangle () {
setTimeout(function() {
requestAnimationFrame(drawRectangle);
ctx.clearRect(0, 0, canvas.width, canvas.height);
//Background color
ctx.fillStyle = "yellow";
//Position, size.
var rectWidth = 70;
var rectHeigth = 55;
ctx.fillRect(xPos,yPos,rectWidth,rectHeigth);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
//Data Layer
var dataLayer = ctx.fillText(rand,xPos+(rectWidth/2),yPos+(rectHeigth/2));
xPos += speed;
//Infinite loop for test
if (xPos > 1080) {
xPos = -150;
}
}, 1000 / framesPerSecond);
}
drawRectangle ();
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Moving Blocks</title>
<style>
canvas {background-color: #131217}
body { margin: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="my-canvas"></canvas>
</body>
</html>
Animating arrays of objects.
For animations you are best of using a single render function that renders all the objects once a frame, rather than create a separate render frame per object.
As for the squares there are many ways that you can get them to do what you want. It is a little difficult to answer as what you want is not completely clear.
This answer will use a rectangle object that has everything needed to be rendered and move. The rectangles will be kept in an array and the main render function will update and render each rectangle in turn.
There will be a spawn function that creates rectangles untill the limit has been reached.
// constants up the top
const quote = ["I", "II", "III"];
// function selects a random Item from an array
const randItem = (array) => array[(Math.random() * array.length) | 0];
// array to hold all rectangles
const rectangles = [];
var maxRectangles = 20;
const spawnRate = 50; // number of frames between spawns
var spawnCountdown = spawnRate;
//Referencing canvas
const ctx = canvas.getContext("2d");
var w, h; // global canvas width and height.
resizeCanvas(); // size the canvas to fit the page
requestAnimationFrame(mainLoop); // this will start when all code below has been run
function mainLoop() {
// resize in the rendering frame as using the resize
// event has some issuse and this is more efficient.
if (w !== innerWidth || h !== innerHeight) {
resizeCanvas();
}
ctx.clearRect(0, 0, w, h);
spawnRectangle(); // spawns rectangles
updateAllRectangles(); // moves all active rectangles
drawAllRectangles(); // I will let you gues what this does... :P
requestAnimationFrame(mainLoop);
}
function resizeCanvas() {
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
// and reset any canvas constants
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
// function to spawn a rectangle
function spawnRectangle() {
if (rectangles.length < maxRectangles) {
if (spawnCountdown) {
spawnCountdown -= 1;
} else {
rectangles.push(
createRectangle({
y: canvas.height / 2, // set at center
text: randItem(quote),
dx: 2, // set the x speed
})
);
spawnCountdown = spawnRate;
}
}
}
// define the default rectangle
const rectangle = {
x: -40, // this is the center of the rectangle
y: 0,
dx: 0, // delta x and y are the movement per frame
dy: 0,
w: 70, // size
h: 55,
color: "yellow",
text: null,
textColor: "black",
draw() { // function to draw this rectangle
ctx.fillStyle = this.color;
ctx.fillRect(this.x - this.w / 2, this.y - this.h / 2, this.w, this.h);
ctx.fillStyle = this.textColor;
ctx.fillText(this.text, this.x, this.y);
},
update() { // moves the rectangle
this.x += this.dx;
this.y += this.dy;
if (this.x > canvas.width + this.w / 2) {
this.x = -this.w / 2;
// if the rectangle makes it to the other
// side befor all rectangles are spawnd
// then reduce the number so you dont get any
// overlap
if (rectangles.length < maxRectangles) {
maxRectangles = rectangles.length;
}
}
}
}
// creats a new rectangle. Setting can hold any unique
// data for the rectangle
function createRectangle(settings) {
return Object.assign({}, rectangle, settings);
}
function updateAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].update();
}
}
function drawAllRectangles() {
var i;
for (i = 0; i < rectangles.length; i++) {
rectangles[i].draw();
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
background-color: #131217;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>

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>
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>
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);
});

canvas drawing margin correction

I'm trying some draw techniques I found in this URL:
http://perfectionkills.com/exploring-canvas-drawing-techniques/
I just noticed that the higher level css properties are not applied to the canvas element mouse events. Is there a easy way to fix this?
<head>
<meta charset="utf-8">
<title>Spray Can</title>
<style>
body {
margin: 0;
padding: 0;
}
#container {
border: 1px solid #ccc;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#canvas {
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var isDrawing;
canvas.onmousedown = function(e) {
isDrawing = true;
context.moveTo(e.clientX, e.clientY);
};
canvas.onmousemove = function(e) {
if (isDrawing) {
var k = 4;
var radgrad = context.createRadialGradient(e.clientX, e.clientY, k, e.clientX, e.clientY, k * 2);
radgrad.addColorStop(0, 'rgba(0,0,0,1)');
radgrad.addColorStop(0.5, 'rgba(0,0,0,0.5)');
radgrad.addColorStop(1, 'rgba(0,0,0,0)');
context.fillStyle = radgrad;
context.fillRect(e.clientX - k * 2, e.clientY - k * 2, k * 2 * 2, k * 2 * 2);
}
};
canvas.onmouseup = function() {
isDrawing = false;
};
});
</script>
</head>
<body>
<div id="container">
<canvas id="canvas" width="400" height="400"></canvas>
</div>
</body>
https://jsfiddle.net/crpq8t5q/1/
What you need is to convert your mouse event's coordinates to be relative to the canvas ones.
Since here you don't touch the scale nor rotation this is just a simple canvasX = mouseX - canvas.offsetLeft and canvasY = mouseY - canvas.offsetTop.
These offsetXXX properties are available on the canvas, but you can also use getBoundingClientRect()which will return better results if your css is more complicated (e.g nested elements with different scrollable areas).
But since this offset will change each time you scroll or resize the page, you need to update these values.
Also, it is a very bad idea to create a readialGradient in a mouse event. This event can fire at really high rate, and creating gradients eats memory.
It is then better to create a single gradient, and modify your whole context's matrix so that the gradient be placed at the mouse coordinates :
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var isDrawing;
var k = 4;
// create the gradient only once
var radgrad = context.createRadialGradient(0, 0, k, 0, 0, k * 2);
radgrad.addColorStop(0, 'rgba(0,0,0,1)');
radgrad.addColorStop(0.5, 'rgba(0,0,0,0.5)');
radgrad.addColorStop(1, 'rgba(0,0,0,0)');
// get our canvas margins;
var rect;
function getRect() {
rect = canvas.getBoundingClientRect();
}
canvas.onmousedown = function(e) {
isDrawing = true;
context.moveTo(e.clientX, e.clientY);
};
canvas.onmousemove = function(e) {
if (isDrawing) {
// normalize our mouse event's coordinates
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
// change the canvas matrix coordinates so we draw at mouse positions
context.setTransform(1, 0, 0, 1, x, y)
context.fillStyle = radgrad;
context.fillRect(-k * 2, -k * 2, k * 2 * 2, k * 2 * 2);
}
};
canvas.onmouseup = function() {
isDrawing = false;
};
var debouncing = false;
function resizeHandler() {
debouncing = false;
getRect();
}
window.onscroll = window.onresize = function() {
// debounce the events
if (!debouncing) {
requestAnimationFrame(resizeHandler);
}
debouncing = true;
}
getRect();
body {
margin: 0;
padding: 0;
}
#container {
border: 1px solid #ccc;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#canvas {}
<div id="container">
<canvas id="canvas" width="400" height="400"></canvas>
</div>

Categories

Resources