Why does a redraw loop alias my HTML5 canvas? - javascript

I've been having difficulty with producing crisp shapes on my canvas, I've been taking out code to try and debug it and it seems one of the causes is the fact that's being redrawn. Below is the code without a loop:
var context = $('canvas')[0].getContext('2d');
function draw() {
context.clearRect(0, 0, context.width, context.height);
context.beginPath();
context.arc(100, 100, 50, 0, 2 * Math.PI, false);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 4;
context.strokeStyle = 'darkred';
context.stroke();
}
draw();
<canvas width=200 height=200></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
And here is the same again with the loop:
var context = $('canvas')[0].getContext('2d');
function draw() {
requestAnimationFrame(draw);
context.clearRect(0, 0, context.width, context.height);
context.beginPath();
context.arc(100, 100, 50, 0, 2 * Math.PI, false);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 4;
context.strokeStyle = 'darkred';
context.stroke();
}
function init() {
if (typeof game_loop != "undefined") clearInterval(game_loop);
var game_loop = setInterval(draw, 30);
}
init();
<canvas width=200 height=200></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
For those who do not see what I do in Chrome, here's an image:
It would seem that even putting the line requestAnimationFrame(draw); in the first example causes the same issue.
What am I doing wrong here?

The problem is that you cannot access context.width and context.height. Therefore this will cause the clearRect method to fail and have the circles being drawn over another.
Use canvas.widthand canvas.height.
var canvas = $('canvas')[0]
var context = canvas.getContext('2d');
var game_loop
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(100, 100, 50, 0, 2 * Math.PI, false);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 4;
context.strokeStyle = 'darkred';
context.stroke();
}
function init() {
if (typeof game_loop != "undefined") clearInterval(game_loop);
game_loop = setInterval(requestAnimationFrame.bind(null, draw), 30) // use requestAnimationFrame but limit to 30ms interval - from #JoshuaK
}
init();
<canvas width=200 height=200></canvas>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
Also you used both, an interval and the requestAnimationFrame. Which will cause massive lags. Use intervals for anmation only when you have no other option. You could also use this polyfill:
window.requestAnimFrame = function(){
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(func){
window.setTimeout(func, 30);
}
);
}()

If you changed the transformation matrix (for example using scale, rotation, or translation), context.clearRect (0,0, canvas.width, canvas.height) will probably not remove the entire visible part Canvas
The solution? Reset the transformation matrix before clearing the canvas:
// Store the current transformation matrix
context.save();
// Use the identity matrix while clearing the canvas
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
// Restore the transform
context.restore();

CanvasRenderingContext2D (the type of context) has no width and height attribute, but it keeps a reference to the canvas it belongs to.
And of course that has width/height, so the minimal change in your code could be:
context.clearRect(0, 0, context.canvas.width, context.canvas.height);

Related

How to fully fade out contents in canvas

This question has been asked twice without the caveat of "Fully Fade Out"
Fastest way of fading out entire contents of a canvas to transparency, not other color
HTML5: Apply transparency to Canvas after drawing through JavaScript
Both of the accepted answers only partially fade out the contents. They both suggest something like:
// semi functional code, but doesn't fully work
ctx.save();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "destination-in";
ctx.fillStyle = "rgba(0, 0, 0, 0.9)";
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
ctx.restore();
This leaves residue everywhere, never fully fading out anything. See example below:
let canvas = document.getElementsByTagName('canvas')[0];
let ctx = canvas.getContext('2d');
let rand = n => Math.floor(Math.random() * n);
setInterval(() => {
ctx.beginPath();
ctx.arc(rand(300), rand(120), rand(60), Math.PI * 2, 0);
ctx.fillStyle = `rgba(${rand(256)}, ${rand(256)}, ${rand(256)}, 1)`;
ctx.globalCompositeOperation = 'source-over';
ctx.fill();
}, 1000);
let fadeOut = () => {
let fadeAmount = 0.05;
// Note that the colour here doesn't matter! Only the alpha matters.
// The colour here is red, but you'll see no red appear
ctx.fillStyle = `rgba(255, 0, 0, ${1 - fadeAmount})`;
ctx.globalCompositeOperation = 'destination-in';
ctx.fillRect(0, 0, 300, 120);
requestAnimationFrame(fadeOut);
};
requestAnimationFrame(fadeOut);
canvas { border: 3px solid #808080; background-color: #000000; }
<canvas width="300" height="120"></canvas>
The question is: How can one fully fade out elements on a canvas, all the way to transparent?
EDIT: I'm searching for a performant solution that works for heavily layered (think visualizer) situations.
Here is a small sample using globalAlpha, looks good to me, no residue...
each FadingCircle will have own fade, that will determine how fast it fades and if it goes to 0 or below we do not draw it, seems like an easy solution.
You can add colors, random positions and change it as much as you like to suit your needs.
const canvas = document.getElementsByTagName('canvas')[0];
const ctx = canvas.getContext('2d');
class FadingCircle {
constructor(x, y, radius, fade) {
this.x = x
this.y = y
this.radius = radius
this.fade = fade
this.globalAlpha = 1
}
draw(ctx) {
if (this.globalAlpha > 0) {
ctx.beginPath()
ctx.globalAlpha = this.globalAlpha
ctx.arc(this.x, this.y, this.radius, Math.PI * 2, 0)
ctx.fill()
this.globalAlpha -= this.fade
}
}
}
let sml = new FadingCircle(40, 50, 20, 0.01)
let med = new FadingCircle(140, 50, 30, 0)
let big = new FadingCircle(100, 50, 50, 0.005)
let animation = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
big.draw(ctx)
med.draw(ctx)
sml.draw(ctx)
requestAnimationFrame(animation);
};
requestAnimationFrame(animation);
<canvas width="300" height="120"></canvas>

Javascript Canvas clear/ redraw

I've tried googling the answer to this but i'm just going around in circles....
If I clear the rect (using clearRect) then the image doesn't redraw after.
However, if I don't clear the images just stack.
What I want it to clear the current image and then draw with new one.
What am I missing?
Does it have something to do with the image Load ?
Sorry if this is a question repeat, I couldn't find the exact answer to this- and I tried what others suggested but results were poor.
http://jsfiddle.net/bxeuhh4h/
function clear() {
var canvasTemp = document.getElementById(imgSection);
var ctxTemp = canvasTemp.getContext("2d");
ctxTemp.clearRect(0, 0, 500, 500);
}
function fillColorOrPattern(imgSection,currentcolor){
if ((oldcolor !== currentcolor) || (oldxImgToBeFilled !== xImgToBeFilled)){
clear();
}
imgFill.onload = function () {
imgToBeFilled.onload = function () {
if ((oldcolor !== currentcolor) || (oldxImgToBeFilled !== xImgToBeFilled)){
fill(imgSection,currentcolor)
}
};
imgToBeFilled.src = xImgToBeFilled;
}
imgFill.src = xImgFill;
}
function fill(imgSection,currentcolor){
canvas = document.getElementById(imgSection);
ctx = canvas.getContext("2d");
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.globalCompositeOperation = "source-atop";
console.log(isItColorOrPattern);
if (isItColorOrPattern == "color"){
ctx.rect(0, 0, canvas.width, canvas.height);
console.log("currentcolor: " + currentcolor);
ctx.fillStyle = getColor(currentcolor);
console.log(getColor(currentcolor));
ctx.fill();
}else{
var pattern = ctx.createPattern(imgFill, 'repeat');
console.log("canvas.width: " + canvas.width);
console.log("xImgFill: " + xImgFill);
console.log(canvas.getContext);
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
}
ctx.globalAlpha = .10;
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
oldcolor = currentcolor;
oldxImgToBeFilled = xImgToBeFilled;
}
$(window).load(function(){
imgToBeFilled = new Image();
imgFill = new Image();
fillColorOrPattern(imgSection,currentcolor);
}
You need to add a beginPath() in there. rect() will accumulate rectangles to the path, clearRect() won't clear those. Also reset comp. mode and alpha as they are sticky.
You could avoid beginPath() if you use fillRect() instead of rect() + fill() (added example below) as fillRect() does not add to the path.
function fill(imgSection,currentcolor){
// these should really be initialized outside the loop
canvas = document.getElementById(imgSection);
ctx = canvas.getContext("2d");
// clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// clear path
ctx.beginPath();
// use default comp. mode
ctx.globalCompositeOperation = "source-over";
// reset alpha
ctx.globalAlpha = 1;
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.globalCompositeOperation = "source-atop";
if (isItColorOrPattern === "color"){
// rect() accumulates on path
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = getColor(currentcolor);
ctx.fill();
// instead of rect() + fill() you could have used:
// fillRect() does not accumulate on path
// fillRect(0, 0, canvas.width, canvas.height);
}
else {
var pattern = ctx.createPattern(imgFill, 'repeat');
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
}
ctx.globalAlpha = .1;
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
ctx.drawImage(imgToBeFilled, 0, 0);
oldcolor = currentcolor;
oldxImgToBeFilled = xImgToBeFilled;
}
Canvas workflow goes like this:
Draw some things on the canvas.
Calculate changes to the position of those things.
Clear the canvas.
Redraw all the things in their new positions.
Canvas does not "remember" where it drew your things so you cannot directly order your things to move.
But you can save the definition of your things in javascript object:
var myCircle={
centerX:50,
centerY:50,
radius:25,
fill:'blue'
}
Then you can "move" your things using the javascript objects:
myCircle.centerX += 5;
And then redraw the things at their new positions. Putting the redraw code in a function makes redrawing easier:
function redraw(){
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
// redraw one or more things based on their javascript objects
ctx.beginPath();
ctx.arc( myCircle.centerX, myCircle.centerY, myCircle.radius, 0, Math.PI*2 );
ctx.closePath();
ctx.fillStyle=myCircle.fill;
ctx.fill();
}
Putting it all together:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var myCircle={
centerX:50,
centerY:50,
radius:25,
fill:'blue'
}
redraw();
document.getElementById('move').addEventListener('click',function(){
myCircle.centerX+=5;
redraw();
});
function redraw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.arc( myCircle.centerX, myCircle.centerY, myCircle.radius, 0, Math.PI*2 );
ctx.closePath();
ctx.fillStyle=myCircle.fill;
ctx.fill();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<button id=move>Move</button>
<br>
<canvas id="canvas" width=300 height=300></canvas>

Resize HTML5 Canvas Circle onclick

Trying to double the size of a circle onclick(). Doesn't seem to be working?
HTML
<canvas id="drawing" width="600px" height="600px" onclick="resize()"></canvas>
Javascript
window.onload = function() {
canvas=document.getElementById("drawing");
context=canvas.getContext("2d");
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 70;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
}
function resize () {
context.fillStyle= "#701be0"; // This changes the rectangle to blue
context.fill();
context.scale(10.5, 2.5);
}
You need to redraw your circle again and also keep in mind that context.scale() will also scale its position so i wouldn't recommend to do it this way. You can just draw a new circle over with bigger radius.
jsFiddle - http://jsfiddle.net/DSFMq/
Here's a jsFiddle for exactly what I needed, in case anyone needs help.
http://jsfiddle.net/elijahmurray/fHv82/1/
x=200;
y=200;
size=100;
radius=30;
function animate() {
reqAnimFrame = window.mozRequestAnimationFrame || //get framerate
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame ||
window.oRequestAnimationFrame
;
reqAnimFrame(animate);
if(radius <= 200) {
radius +=3;
} //increase size by 1 per frame
draw();
}
function draw() {
var canvas = document.getElementById("ex1");
var context = canvas.getContext("2d");
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
}
animate();

Image context.restore();

Im starting to learn canvas and i just hit my first frustrating situation, im trying to make a clipping mask of a .jpg src in a triangle. Everything looks fine until i restore my context and try to add any other Path... my clipping mask appears to not exist anymore.
Here is my code :
<script>
function init() {
var canvas = document.getElementById('myCanvas');
if(canvas.getContext) {
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.src = 'stephgopro2.jpg';
// triangle coordonate
context.save();
context.beginPath;
context.moveTo(100, 0);
context.lineTo(0, 100);
context.lineTo(200, 100);
context.lineTo(100, 0);
context.clip();
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0, 300, 170);
};
context.restore();
context.beginPath();
context.fillStyle = '#333';
context.rect(0, 0, 600, 200);
context.fill();
}
}
</script>
</head>
<body onload='init()'>
<canvas id="myCanvas" width="600" height="200"></canvas>
</body>
Can you please help me with that? many thanks.
The image is loaded asynchronously so the context has already been restored before the image is drawn to the canvas. If you update the code as follows you'll get (what I think are) the results you expect:
function init() {
var canvas = document.getElementById('myCanvas');
if(canvas.getContext) {
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.src = 'assets/1.jpg';
// triangle coordonate
context.save();
context.beginPath();
context.moveTo(100, 0);
context.lineTo(0, 100);
context.lineTo(200, 100);
context.lineTo(100, 0);
context.stroke();
context.clip();
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0, 300, 170);
// Restore the context and continue drawing now the image has been drawn
context.restore();
context.beginPath();
context.fillStyle = '#333';
context.rect(0, 0, 600, 200);
context.fill();
};
}
}

Whole canvas is being rotated?

I am just trying my hands at HTML5's most talked feature i.e canvas. There are 2 rectangles and I only want to rotate 1st and keep the 2nd one as it is. Problem is when the below code runs my whole canvas is rotated and both rectangles are rotated. I can't even find any API where I can get reference to the object that I have drawn and rotate only that specific object instead of the whole context.
Code:
<script type="text/javascript">
var context;
var radian = 0.01;
var w, h;
$(document).ready(function () {
w = document.width;
h = document.height;
var canvas = $('#canvas');
context = canvas[0].getContext('2d');
canvas[0].width = w;
canvas[0].height = h;
setInterval(startAnim, 200);
});
function startAnim() {
context.clearRect(0, 0, w, h);
context.strokeStyle = 'rgb(0,0,0)';
context.fillStyle = 'rgb(0,0,0)';
context.fillRect(0, 0, w, h);
context.strokeStyle = 'rgb(0,0,0)';
context.fillStyle = 'rgb(255,255,0)';
context.rotate(radian);
context.strokeRect(400, 300, 200, 200);
context.fillRect(400, 300, 200, 200);
context.fillStyle = 'rgb(0,255,255)';
context.fillRect(500, 400, 200, 200);
radian += 0.01;
}
</script>
How can I prevent this from happening?
Finally, I have resolved the issue on my own by trial and error. This is the code:
<script type="text/javascript">
var context;
var radian = 0.01;
var w, h;
$(document).ready(function () {
w = document.width;
h = document.height;
var canvas = $('#canvas');
context = canvas[0].getContext('2d');
canvas[0].width = w;
canvas[0].height = h;
setInterval(startAnim, 100);
});
function startAnim() {
context.save();
context.strokeStyle = 'rgb(0,0,0)';
context.fillStyle = 'rgb(0,0,0)';
context.fillRect(0, 0, w, h);
context.rotate(radian);
context.strokeStyle = 'rgb(0,0,0)';
context.fillStyle = 'rgb(255,255,0)';
context.strokeRect(400, 300, 200, 200);
context.fillRect(400, 300, 200, 200);
context.restore();
context.fillStyle = 'rgb(0,255,255)';
context.fillRect(500, 400, 200, 200);
radian += 0.1;
}
</script>
As I understand you have to clear the whole canvas and redraw everything, between each frame. Keep the data for the rectangles in memory. Do the context.clearRect(0, 0, w, h); between each frame, then draw the rectangles again with there new altered properties.

Categories