HTML Canvas reset drawing point after transform and rotate - html

I have this simple canvas webpage that lets user upload photo from camera by using HTML input type file. The idea is to let user make free drawing on their image. However, I have one problem.
On some devices, the image from camera is drawn onto the canvas with wrong orientation, so I have to provide users a button to rotate their image to get the drawing with correct orientation.
The problem is that after the canvas has been transformed and rotated to get the correct orientation, the drawing coordinates seems to be way off. For example, if I draw straight horizontal line, I get instead straight vertical line after the image has been rotated once. I think the problem lies in that fact that canvas orientation is changed.
So how can I correct back the drawing coordinate after image has been transformed and rotate? My code is below..
window.onload = init;
var canvas, ctx, file, fileURL;
var mousePressed = false;
var lastX, lastY;
function init(){
canvas = document.getElementById('myCanvas')
ctx = canvas.getContext('2d')
canvas.addEventListener('mousedown', touchstartHandler, false)
canvas.addEventListener('mousemove', touchmoveHandler, false)
canvas.addEventListener('mouseup', touchendHandler, false)
canvas.addEventListener('mouseleave', touchcancelHandler, false)
}
function touchstartHandler(e){
e.preventDefault()
mousePressed = true;
Draw(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, false);
}
function touchmoveHandler(e){
e.preventDefault()
if (mousePressed) {
Draw(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
}
}
function touchendHandler(e){
e.preventDefault()
if (mousePressed) {
mousePressed = false;
}
}
function touchcancelHandler(e){
e.preventDefault()
if (mousePressed) {
mousePressed = false;
}
}
function Draw(x, y, isDown) {
if (isDown) {
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 12;
ctx.lineJoin = "round";
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke();
}
lastX = x;
lastY = y;
}
<!DOCTYPE html>
<html>
<head>
<title>Portrait</title>
</head>
<body>
<canvas id="myCanvas"></canvas><br/>
<input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera"><br/><br/>
<button onclick="rotate()">Rotate</button>
<script>
var file, canvas, ctx, image, fileURL;
function fileUpload(files){
file = files[0]
fileURL = URL.createObjectURL(file)
canvas = document.getElementById('myCanvas')
canvas.style.backgroundColor = "blue"
ctx = canvas.getContext('2d')
image = new Image()
image.onload = function() {
canvas.width = 500
canvas.height = (500*this.height)/this.width
ctx.drawImage(image,0,0,canvas.width,canvas.height)
ctx.save();
}
image.src = fileURL
}
function rotate(){
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.translate(canvas.width/2, canvas.height/2)
ctx.rotate(90*Math.PI/180)
ctx.translate(-canvas.width/2, -canvas.height/2)
ctx.drawImage(image,0,0,canvas.width,canvas.height)
}
</script>
</body>
</html>

You need to save the canvas state before rotating and translating, and then restore the state when the transformation is done.
var file, canvas, ctx, image, fileURL, rotation = 90;
function fileUpload(files) {
file = files[0]
fileURL = URL.createObjectURL(file)
canvas = document.getElementById('myCanvas')
canvas.style.backgroundColor = "blue"
ctx = canvas.getContext('2d')
image = new Image()
image.onload = function() {
canvas.width = 500
canvas.height = (500 * this.height) / this.width
ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
}
image.src = fileURL
}
function rotate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save(); //save canvas state
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(rotation * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
rotation += 90;
ctx.restore(); //restore canvas state
}
canvas {border: 1px solid red}
<canvas id="myCanvas"></canvas>
<br/>
<input type="file" onchange="fileUpload(this.files)" id="file-input" capture="camera">
<br/>
<br/>
<button onclick="rotate()">Rotate</button>

Simple rotation
Quickest way to rotate the image by steps of 90 deg
ctx.setTransform(
0,1, // direction of x axis
-1,0 // direction of y axis
canvas.width,0 // location in pixels of the origin (0,0)
);
Then draw the image
ctx.drawImage(image,0,0);
Rather than use ctx.restore() that can be slow in many situations you can eset only the transform to the default with.
ctx.setTransform(1,0,0,1,0,0);
Rotate 90, 180, -90deg
Thus to rotate 90 deg
ctx.setTransform(0,1,-1,0,canvas.width,0);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
Thus to rotate 180 deg
ctx.setTransform(-1,0,0,-1,canvas.width,canvas.height);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);
Thus to rotate -90 deg
ctx.setTransform(0,-1,1,0,0,canvas.height);
ctx.drawImage(image,0,0);
ctx.setTransform(1,0,0,1,0,0);

Related

Create pattern with canvas

I would like to create a pettern with canvas. The Picture which should be used should also be gernerated first. I already did something like this with this code:
document.addEventListener('DOMContentLoaded', function () {
async function draw() {
var canvas = document.getElementById('canvas1')
var ctx = canvas.getContext("2d");
var canvas = ctx.createImageData(500, 300);
ctx.fillStyle = "#7289DA";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Select the color of the stroke
ctx.strokeStyle = '#74037b';
// Draw a rectangle with the dimensions of the entire canvas
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 70px sans-serif';
// Select the style that will be used to fill the text in
ctx.save();
ctx.rotate(1.7*Math.PI);
ctx.fillStyle = '#23272A';
ctx.fillText('Text', -70, 300);
ctx.restore();
// Actually fill the text with a solid color
}
draw();
});
<canvas id="canvas" width="1500" height="900">Beispiel für eine Kachelung eines Musters in Canvas.</canvas>
Now I want to create some kind of grid with it, it should look like this
How can I do that?
The best way would be using two for loops to go over the x and y values! You can surround the part that draws text with these loops and use the changing x and y values instead of hard-coded ones.
async function draw() {
var canvas = document.getElementById('canvas1')
var ctx = canvas.getContext("2d");
var canvas = ctx.createImageData(500, 300);
ctx.fillStyle = "#7289DA";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Select the color of the stroke
ctx.strokeStyle = '#74037b';
// Draw a rectangle with the dimensions of the entire canvas
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 70px sans-serif';
ctx.fillStyle = '#23272A';
// Select the style that will be used to fill the text in
for (var x = 0; x < canvas.width; x += 100 ) { // 100 is the width
for (var y = 0; y < canvas.height; y += 70) { // 70 is the height
ctx.save();
ctx.translate(x, y); // offset the text
ctx.rotate(1.7*Math.PI);
ctx.fillText('Text', -70, 300);
ctx.restore();
// Actually fill the text with a solid color
}
}
}
The reason ctx.translate(x, y) is used instead of ctx.fillText('Text', x - 70, y + 300) is because using fillText would move the grid at an angle instead of just rotating the letters.

HTML Canvas to WPF XAML Canvas

I have an ASP.NET application that allows users to click or tap on a Canvas to indicate pain locations on a body image. A body image is displayed on the Canvas and is the same size as the Canvas.
function drawBodyMap() {
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
var imageObj = new Image();
imageObj.src = 'https://.../body.jpg';
imageObj.onload = function () {
ctx.drawImage(imageObj, 0, 0, 600, 367);
};
}
<canvas id="myCanvas" width="600" height="367"></canvas>
<script>
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.addEventListener('mouseup', function (evt) {
if (ixPos > 9)
return;
var mousePos = getMousePos(canvas, evt);
bodyX[ixPos] = mousePos.x;
bodyY[ixPos] = mousePos.y;
painType[ixPos] = pain_type;
ixPos++;
ctx.beginPath();
ctx.arc(mousePos.x, mousePos.y, 8, 0, 2 * Math.PI);
if (pain_type == 1)
ctx.fillStyle = "#DC143C";
else if (pain_type == 2)
ctx.fillStyle = "#EA728A";
else if (pain_type == 3)
ctx.fillStyle = "#DAA520";
else if (pain_type == 4)
ctx.fillStyle = "#008000";
else if (pain_type == 5)
ctx.fillStyle = "#4169E1";
ctx.fill();
}, false);
</script>
The X,Y points added to the Canvas on the body image are saved to a database. These points are then loaded into a WPF application that displays the same body image on an XAML Canvas. C# code then adds the points over the image.
WPF CODE:
private void DisplayBodyPain()
{
List<BodyPain> pain = gFunc.sws.GetBodyPain(MemberID);
foreach (BodyPain bp in pain)
{
Border b = new Border();
b.Tag = bp.PainType.ToString();
b.Cursor = Cursors.Hand;
b.Width = 16;
b.Height = 16;
b.CornerRadius = new CornerRadius(8);
b.Background = GetPainBrush((byte)bp.PainType);
cvsBody.Children.Add(b);
Canvas.SetTop(b, bp.YPos);
Canvas.SetLeft(b, bp.XPos);
}
}
The problem I have is that the points drawn on the XAML Canvas are all slightly different from the points that were drawn on the HTML Canvas. Each point is not in exactly the same location.
Is there a way I can fix this? Should I be doing it differently?
HTML Canvas
WPF Canvas
I think you need to subtract the size of the marker from the coordinate where you want to place it. For the last two lines, try this instead:
Canvas.SetTop(b, bp.YPos - (b.Height / 2));
Canvas.SetLeft(b, bp.XPos - (b.Width / 2));
By subtracting half the marker's height and width, the center of the marker is placed on the desired coordinates.

HTML Canvas X and Y values: How to get them?

So, I have a canvas, and I have a bunch of functions on it, but I was trying to make it so that it would show the x and y positions of the mouse on the canvas in the label above. But, when I run the mouse over, the x and y positions of the mouse on the canvas do not match the x and y positions shown on the label, and I cannot figure out why. Can you please help? My Code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Canvas Library</title>
</head>
<body>
<p id="text">Unknown</p>
<canvas width="400" height="400" id="canvas"></canvas>
<script>
var Canvas = document.getElementById("canvas");
var label = document.getElementById("text");
//Declare the Canvas Object
var canvas = function(src) {
//The Canvas to draw on
this.src = src;
//The context of source(used for drawing)
this.ctx = this.src.getContext("2d");
//The Mouse Move Function
this.showCoordinates = function(e) {
console.log(e);
label.innerHTML = "<b>x: </b>" + e.layerX + " <b>y: </b>" + e.layerY;
}
//Show coordinates variable
this.showCoordinatesBool = true;
//The boolean to tell if we should use stroke
var useStroke = true;
//The fill style and stroke style(can be color, pattern, or gradient)
this.fillStyle = "#000000";
this.strokeStyle = "#000000";
//The Line cap style (can be butt, square, or round)
this.lineCap = "butt";
//The Stroke Weight (how wide the strokes are)
this.strokeWeightVar = "default";
//The corner style (how the corners are drawn)
this.cornerStyle = "miter";
//Function to set the fill style
this.fill = function(style) {
this.fillStyle = style;
this.ctx.fillStyle = style;
}
//Function to set the stroke style
this.stroke = function(style) {
this.useStroke = true;
this.strokeStyle = style;
this.ctx.strokeStyle = style;
}
//Function to delete the stroke
this.noStroke = function() {
this.useStroke = false;
}
//Function to draw a rectangle
this.rect = function(x, y, width, height) {
this.ctx.fillRect(x, y, width, height);
if(this.useStroke) {
this.ctx.strokeRect(x, y, width, height);
}
}
//Function to draw a corner
this.corner = function(style, startX, startY, x1, y1, x2, y2) {
this.ctx.lineJoin = style;
this.cornerStyle = style;
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
}
//Function to draw a hollow rectangle
this.hollowRect = function(x, y, width, height) {
this.ctx.strokeRect(x, y, width, height);
}
//Function to set the canvas background
this.background = function(style) {
this.fillStyle = style;
this.ctx.fillStyle = style;
this.ctx.fillRect(0, 0, this.src.width, this.src.height);
}
//Function to draw a line
this.line = function(startX, startY, endX, endY) {
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(endX, endY);
this.ctx.stroke();
}
//Function to change line style
this.lineCap = function(mode) {
this.ctx.lineCap = mode;
this.lineCap = mode;
}
//Function to change stroke weight
this.strokeWeight = function(weight) {
this.ctx.lineWidth = weight;
this.strokeWeightVar = weight;
}
//Function to clear a certain area
this.clearArea = function(x, y, width, height) {
this.ctx.clearRect(x, y, width, height);
}
//Turn the show coordinate function on
this.enableCoordinates = function() {
this.showCoordinatesBool = true;
this.src.addEventListener("mousemove", this.showCoordinates);
}
}
//Create a new canvas
var myCanvas = new canvas(Canvas);
//Set the Background Color
myCanvas.background("#ff0000");
//Set the fill color
myCanvas.fill("#0000ff");
//Set the Stroke Color
myCanvas.stroke("#00ff00");
//Draw a rectangle
myCanvas.rect(164, 153, 50, 100);
//Draw a hollow rectangle
myCanvas.hollowRect(300, 300, 50, 50);
//Disable the Stroke
myCanvas.noStroke();
//Draw a rectangle with no stroke
myCanvas.rect(21, 18, 50, 50);
//Change the Stroke color
myCanvas.stroke("#ffff00");
//Change the stroke weight
myCanvas.strokeWeight(10);
//Change the line cap
myCanvas.lineCap("round");
//Draw a line
myCanvas.line(350, 30, 250, 80);
//Draw a corner
myCanvas.corner("miter", 50, 135, 100, 185, 100, 110)
//Enable the Coordinates
myCanvas.enableCoordinates();
//Clear a space from the canvas
myCanvas.clearArea(6, 245, 100, 100);
</script>
</body>
</html>
You can get proper mouse coordinates relative to the canvas, using offsetX and offsetY property of MouseEvent.
...
this.showCoordinates = function(e) {
label.innerHTML = "<b>x: </b>" + e.offsetX + " <b>y: </b>" + e.offsetY;
}
...
var Canvas = document.getElementById("canvas");
var label = document.getElementById("text");
//Declare the Canvas Object
var canvas = function(src) {
//The Canvas to draw on
this.src = src;
//The context of source(used for drawing)
this.ctx = this.src.getContext("2d");
//The Mouse Move Function
this.showCoordinates = function(e) {
//console.log(e);
label.innerHTML = "<b>x: </b>" + e.offsetX + " <b>y: </b>" + e.offsetY;
}
//Show coordinates variable
this.showCoordinatesBool = true;
//The boolean to tell if we should use stroke
var useStroke = true;
//The fill style and stroke style(can be color, pattern, or gradient)
this.fillStyle = "#000000";
this.strokeStyle = "#000000";
//The Line cap style (can be butt, square, or round)
this.lineCap = "butt";
//The Stroke Weight (how wide the strokes are)
this.strokeWeightVar = "default";
//The corner style (how the corners are drawn)
this.cornerStyle = "miter";
//Function to set the fill style
this.fill = function(style) {
this.fillStyle = style;
this.ctx.fillStyle = style;
}
//Function to set the stroke style
this.stroke = function(style) {
this.useStroke = true;
this.strokeStyle = style;
this.ctx.strokeStyle = style;
}
//Function to delete the stroke
this.noStroke = function() {
this.useStroke = false;
}
//Function to draw a rectangle
this.rect = function(x, y, width, height) {
this.ctx.fillRect(x, y, width, height);
if (this.useStroke) {
this.ctx.strokeRect(x, y, width, height);
}
}
//Function to draw a corner
this.corner = function(style, startX, startY, x1, y1, x2, y2) {
this.ctx.lineJoin = style;
this.cornerStyle = style;
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(x1, y1);
this.ctx.lineTo(x2, y2);
this.ctx.stroke();
}
//Function to draw a hollow rectangle
this.hollowRect = function(x, y, width, height) {
this.ctx.strokeRect(x, y, width, height);
}
//Function to set the canvas background
this.background = function(style) {
this.fillStyle = style;
this.ctx.fillStyle = style;
this.ctx.fillRect(0, 0, this.src.width, this.src.height);
}
//Function to draw a line
this.line = function(startX, startY, endX, endY) {
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(endX, endY);
this.ctx.stroke();
}
//Function to change line style
this.lineCap = function(mode) {
this.ctx.lineCap = mode;
this.lineCap = mode;
}
//Function to change stroke weight
this.strokeWeight = function(weight) {
this.ctx.lineWidth = weight;
this.strokeWeightVar = weight;
}
//Function to clear a certain area
this.clearArea = function(x, y, width, height) {
this.ctx.clearRect(x, y, width, height);
}
//Turn the show coordinate function on
this.enableCoordinates = function() {
this.showCoordinatesBool = true;
this.src.addEventListener("mousemove", this.showCoordinates);
}
}
//Create a new canvas
var myCanvas = new canvas(Canvas);
//Set the Background Color
myCanvas.background("#ff0000");
//Set the fill color
myCanvas.fill("#0000ff");
//Set the Stroke Color
myCanvas.stroke("#00ff00");
//Draw a rectangle
myCanvas.rect(164, 153, 50, 100);
//Draw a hollow rectangle
myCanvas.hollowRect(300, 300, 50, 50);
//Disable the Stroke
myCanvas.noStroke();
//Draw a rectangle with no stroke
myCanvas.rect(21, 18, 50, 50);
//Change the Stroke color
myCanvas.stroke("#ffff00");
//Change the stroke weight
myCanvas.strokeWeight(10);
//Change the line cap
myCanvas.lineCap("round");
//Draw a line
myCanvas.line(350, 30, 250, 80);
//Draw a corner
myCanvas.corner("miter", 50, 135, 100, 185, 100, 110)
//Enable the Coordinates
myCanvas.enableCoordinates();
//Clear a space from the canvas
myCanvas.clearArea(6, 245, 100, 100);
<p id="text">Unknown</p>
<canvas width="400" height="400" id="canvas"></canvas>

Canvas, animate line to move up and down

I have created a horizontal line in HTML5 Canvas. I want to animate the line to move infinity up and down. Is it possible?
function horizontal_line() {
context.beginPath();
context.moveTo(100, 100);
context.lineTo(5000, 100);
context.strokeStyle = "Green";
context.stroke();
}
For an animation you will need to have a way to draw different frames, and in each frame you have to delete the previous one, calculate the new position of the line, and then draw the line again:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "Green";
var posY = 0;
var lineLength = 10;
var speed = 2;
function drawLine() {
ctx.beginPath();
ctx.moveTo(10, posY);
ctx.lineTo(10, posY+lineLength);
ctx.stroke();
}
function moveLine () {
posY += speed;
if (posY < 0 || posY > canvas.height) {
speed = speed * -1;
}
}
function loop() {
// clear old frame;
ctx.clearRect(0,0,canvas.width, canvas.height);
moveLine();
drawLine();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
<canvas id="canvas"></canvas>
In this example requestAnimationFrame is what provides you the frames you need, so the function loop() gets called over and over again. In there we clear the old frame with clearRect(), calculate the new position, and then we draw the new Line with drawLine().

moving an image across a html canvas

I am trying to move an image from the right to the center and I am not sure if this is the best way.
var imgTag = null;
var x = 0;
var y = 0;
var id;
function doCanvas()
{
var canvas = document.getElementById('icanvas');
var ctx = canvas.getContext("2d");
var imgBkg = document.getElementById('imgBkg');
imgTag = document.getElementById('imgTag');
ctx.drawImage(imgBkg, 0, 0);
x = canvas.width;
y = 40;
id = setInterval(moveImg, 0.25);
}
function moveImg()
{
if(x <= 250)
clearInterval(id);
var canvas = document.getElementById('icanvas');
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
var imgBkg = document.getElementById('imgBkg');
ctx.drawImage(imgBkg, 0, 0);
ctx.drawImage(imgTag, x, y);
x = x - 1;
}
Any advice?
This question is 5 years old, but since we now have requestAnimationFrame() method, here's an approach for that using vanilla JavaScript:
var imgTag = new Image(),
canvas = document.getElementById('icanvas'),
ctx = canvas.getContext("2d"),
x = canvas.width,
y = 0;
imgTag.onload = animate;
imgTag.src = "http://i.stack.imgur.com/Rk0DW.png"; // load image
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctx.drawImage(imgTag, x, y); // draw image at current position
x -= 4;
if (x > 250) requestAnimationFrame(animate) // loop
}
<canvas id="icanvas" width=640 height=180></canvas>
drawImage() enables to define which part of the source image to draw on target canvas. I would suggest for each moveImg() calculate the previous image position, overwrite the previous image with that part of imgBkg, then draw the new image. Supposedly this will save some computing power.
Here's my answer.
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var myImg = new Image();
var myImgPos = {
x: 250,
y: 125,
width: 50,
height: 25
}
function draw() {
myImg.onload = function() {
ctx.drawImage(myImg, myImgPos.x, myImgPos.y, myImgPos.width, myImgPos.height);
}
myImg.src = "https://mario.wiki.gallery/images/thumb/c/cc/NSMBUD_Mariojump.png/1200px-NSMBUD_Mariojump.png";
}
function moveMyImg() {
ctx.clearRect(myImgPos.x, myImgPos.y, myImgPos.x + myImgPos.width, myImgPos.y +
myImgPos.height);
myImgPos.x -= 5;
}
setInterval(draw, 50);
setInterval(moveMyImg, 50);
<canvas id="canvas" class="canvas" width="250" height="150"></canvas>
For lag free animations,i generally use kinetic.js.
var stage = new Kinetic.Stage({
container: 'container',
width: 578,
height: 200
});
var layer = new Kinetic.Layer();
var hexagon = new Kinetic.RegularPolygon({
x: stage.width()/2,
y: stage.height()/2,
sides: 6,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});
layer.add(hexagon);
stage.add(layer);
var amplitude = 150;
var period = 2000;
// in ms
var centerX = stage.width()/2;
var anim = new Kinetic.Animation(function(frame) {
hexagon.setX(amplitude * Math.sin(frame.time * 2 * Math.PI / period) + centerX);
}, layer);
anim.start();
Here's the example,if you wanna take a look.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-animate-position-tutorial/
Why i suggest this is because,setInterval or setTimeout a particular function causes issues when large amount of simultaneous animations take place,but kinetic.Animation deals with framerates more intelligently.
Explaining window.requestAnimationFrame() with an example
In the following snippet I'm using an image for the piece that is going to be animated.
I'll be honest... window.requestAnimationFrame() wasn't easy for me to understand, that is why I coded it as clear and intuitive as possible. So that you may struggle less than I did to get my head around it.
const
canvas = document.getElementById('root'),
btn = document.getElementById('btn'),
ctx = canvas.getContext('2d'),
brickImage = new Image(),
piece = {image: brickImage, x:400, y:70, width:70};
brickImage.src = "https://i.stack.imgur.com/YreH6.png";
// When btn is clicked execute start()
btn.addEventListener('click', start)
function start(){
btn.value = 'animation started'
// Start gameLoop()
brickImage.onload = window.requestAnimationFrame(gameLoop)
}
function gameLoop(){
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// Draw at coordinates x and y
ctx.drawImage(piece.image, piece.x, piece.y)
let pieceLeftSidePos = piece.x;
let middlePos = canvas.width/2 - piece.width/2;
// Brick stops when it gets to the middle of the canvas
if(pieceLeftSidePos > middlePos) piece.x -= 2;
window.requestAnimationFrame(gameLoop) // Needed to keep looping
}
<input id="btn" type="button" value="start" />
<p>
<canvas id="root" width="400" style="border:1px solid grey">
A key point
Inside the start() function we have:
brickImage.onload = window.requestAnimationFrame(gameLoop);
This could also be written like: window.requestAnimationFrame(gameLoop);
and it would probably work, but I'm adding the brickImage.onload to make sure that the image has loaded first. If not it could cause some issues.
Note: window.requestAnimationFrame() usually loops at 60 times per second.