HTML5 canvas context doesn't clear with clearRect() - javascript

I have two canvases: a buffer and a display canvas whose context gets a drawImage() call with the buffer canvas. I clearRect() both canvases before adding new objects and calling drawImage(), but both display all previously-added objects. This is the object-adding code (check out the full JSFiddle below):
ctx.clearRect(0, 0, 500, 500);
buf.clearRect(0, 0, 500, 500);
img.src = bufCanvas.toDataURL();
buf.fillStyle = 'hsl(' + ~~(Math.random()*360) + ', 100%, 70%)';
buf.rect(Math.random()*w, Math.random()*h, 20, 20);
buf.fill();
ctx.drawImage(bufCanvas, 0, 0);
JSFiddle: http://jsfiddle.net/3movoayv/10/
The top is the buffer (click your mouse there to generate new rects), the middle is the display canvas that I copy the buffer to, and the bottom is an img element that displays the contents of the buffer canvas after I call clearRect() (which always correctly displays nothing).
What's going on here?

You've forgotten to begin a new path each time using .beginPath() (prevents the event stacking issue you're witnessing):
bufCanvas.addEventListener('mousedown', function (e) {
ctx.clearRect(0, 0, 500, 500);
buf.clearRect(0, 0, 500, 500);
img.src = bufCanvas.toDataURL();
buf.beginPath(); // < begin a new path
buf.fillStyle = 'hsl(' + ~~(Math.random()*360) + ', 100%, 70%)';
buf.rect(Math.random()*w, Math.random()*h, 20, 20);
buf.fill();
ctx.drawImage(bufCanvas, 0, 0);
});

Related

HTML5 ( canvas, javascript ) The image, makes traces

enter image description herefirstly sorry for my english. Second, I have a problem with my javascript.
I trying to make something like baner which changes images inside. But when i want to contiune my script, the last image out of canvas field makes me problem. Its makes leaving traces. Its my code.
<html>
<head>
<link rel="stylesheet" href="style.css">
<script>
var cat= new Image();
function init(){
cat.src = 'cat.png';
window.requestAnimationFrame(draw);
}
function draw() {
var context = document.getElementById('spin').getContext('2d');
context.clearRect(0, 0, 530, 110);
context.save();
context.translate(-1, 0);
context.drawImage(cat, 5, 0);
context.drawImage(cat, 110, 0);
context.drawImage(cat, 215, 0);
context.drawImage(cat, 320, 0);
context.drawImage(cat, 425, 0);
context.drawImage(cat, 530, 0); /// its an image with problem
window.requestAnimationFrame(draw);
}
init();
</script>
</head>
<body>
<div class="roulette">
<canvas id="spin" width="530" height="110"></canvas>
</div>
</body>
You never call context.restore(), so you are stacking a lot of CanvasStates in memory (which will make your whole application slow down in few minutes), and more directly noticeable, your context transform is never reset, meaning that at the second call the coordinate 0, 0 will be the pixels at coordinates -1, 0 and at third call -2, 0 etc. which will make your call to clearRect() miss one more pixel on the right side of the canvas every time.
It's quite unclear what you were after, but to correctly clear your context, call context.setTransform(1, 0, 0, 1, 0, 0) before calling clearRect(), this will reset the transformation matrix to its identity matrix.
Then if you want to translate your context by an ever decreasing value, store that value in a variable and use it in your call to translate().
Finally, remove that call to save() because it's not needed.
let x = 0; // the variable where we store the current offset
var cat = new Image();
function init() {
cat.src = 'https://picsum.photos/110/110';
// always wait for your image has loaded before doing anything with it
cat.onload = evt =>
window.requestAnimationFrame(draw);
}
function draw() {
var context = document.getElementById('spin').getContext('2d');
// update the offset
x -= 1;
// last image is hidden, stop the loop?
if (x < (530 + 110) * -1) {
return;
}
// reset the context matrix
context.setTransform(1, 0, 0, 1, 0, 0);
// clear the full canvas
context.clearRect(0, 0, 530, 110);
// set the updated offset
context.translate(x, 0);
context.drawImage(cat, 5, 0);
context.drawImage(cat, 110, 0);
context.drawImage(cat, 215, 0);
context.drawImage(cat, 320, 0);
context.drawImage(cat, 425, 0);
context.drawImage(cat, 530, 0);
window.requestAnimationFrame(draw);
}
init();
<canvas width="530" height="110" id="spin"></canvas>
When loading images in javascript, note that this process is asynchronous. When assigning a value to the src attribute of an Image element, you need to get the element in the onload callback function of this element. Otherwise, when drawing to the canvas, the element may not have fully loaded the corresponding image resource.
const img = new Image()
img.onload = function () {
// img fully loaded here
}
img.src = 'path/to/image/sources'

Can I re-draw a canvas image with its own image?

I'm learning to draw an image with canvas and getting a problem inside this example:
let img = new Image();
img.src = 'https://image.freepik.com/free-photo/hrc-siberian-tiger-2-jpg_21253111.jpg';
let a = function () {
let c1 = document.getElementById('c1'),
c2 = document.getElementById('c2');
c1.width = c2.width = 150;
c1.height = c2.width = 150;
c1.getContext('2d').drawImage(img, 0, 0, 150, 150);
c2.getContext('2d').drawImage(img, 0, 0, 150, 150);
};
let b = function () {
let c2 = document.getElementById('c2');
c2.width = 100;
c2.height = 100;
c2.getContext('2d').drawImage(c2, 0, 0, 100, 100);
};
let c = function () {
let c1 = document.getElementById('c1'),
c3 = document.getElementById('c3');
c3.width = 100;
c3.height = 100;
c3.getContext('2d').drawImage(c1, 0, 0, 100, 100);
};
a();
b();
c();
<div>
<canvas id="c1"></canvas>
</div>
<div>
<canvas id="c2"></canvas>
</div>
<div>
<canvas id="c3"></canvas>
</div>
Inside b function. I want to re-draw (resize) its own image with another size (changing width and height from 150 to 100). But it looks like it couldn't.
Then, I've tried to make another function (c). In this function, I've used the image of canvas c1 to re-draw image of canvas c3. That's ok.
So, my question is: Cannot canvas use its own image to draw an image for itself? (or maybe I've done something wrong)
Edit: At first I thought that using an HTMLCanvasElement in the drawImage() call was an incorrect argument type. That was wrong, it's a valid argument. The actual issue was that the code was not waiting for the image to load.
You need to take a look at how you are getting your initial image data for your first canvas. I would not expect it to work because you could be drawing the image data from img before it is actually loaded. You need to attach callbacks to img to wait for it to finish loading the image and then draw that image on a canvas.
Consider my example below that includes an asynchronous way to load an image, how to draw one canvas in another, and how to draw text (just to show differences between canvases).
function loadImage(path) {
return new Promise((resolve, reject) => {
let img = new Image();
img.addEventListener("load", () => {
resolve(img);
});
img.addEventListener("error", (err) => {
reject(err);
});
img.src = path;
});
}
loadImage("https://cdn.sstatic.net/Sites/stackoverflow/img/sprites.svg")
.then((img) => {
let c1 = document.getElementById("c1"),
c2 = document.getElementById("c2"),
c1Ctx = c1.getContext("2d"),
c2Ctx = c2.getContext("2d");
c1Ctx.drawImage(img, 0, 0, 150, 150);
c1Ctx.strokeText("I'm drawing on canvas 1", 25, 25);
c2Ctx.drawImage(c1, 25, 25, 100, 100);
c2Ctx.strokeText("I'm drawing on canvas 2", 25, 25);
})
.catch(console.error);
<canvas id="c1" width="150" height="150"></canvas>
<canvas id="c2" width="150" height="150"></canvas>
Another thing that you will likely run into since you want to be able to pull data out of your canvas is CORS issues. I point this out explicitly because the image that you are trying to draw onto your canvas is from a different domain (image.freepik.com in your example). Whenever you draw image data from another domain onto a canvas, that canvas becomes tainted an you can no longer use canvas' toBlob(), toDataURL(), or getImageData() methods. See: MDN: CORS enabled image.

clearRect produces white box (not removes set colors)

I have multiple stacked contexts in a canvas (each will be displaying circles, then deleting cirles, creating new circle in new location on the context). I was testing the feasibility with this code:
var radius = 10;
cx2 = document.getElementById("myCanvasID").getContext('2d');
cx2.beginPath();
cx2.arc(150, 150, 10, 0, Math.PI*2, true);
cx2.closePath();
cx2.fillStyle = '#6495ED';
cx2.fill();
cx2.lineWidth = 2;
cx2.strokeStyle = '#003300';
cx2.stroke();
//ctx2.fillColor = "rgba(0-255, 0-255, 0-255, 0-1)"
cx2.clearRect(0,0,300,300);
//cx2.globalCompositeOperation = "destination-out"
//cx2.fillStyle = 'rgba(0, 255, 0, 0)';
// cx2.fillRect(0, 0, 300, 300);
// cx2.globalCompositeOperation = "destination-in"
cx2.beginPath();
cx2.arc(180, 180, 10, 0, Math.PI*2, true);
cx2.closePath();
cx2.fillStyle = '#6495ED';
cx2.fill();
cx2.lineWidth = 2;
cx2.strokeStyle = '#003300';
cx2.stroke();
The issue with clearRect is that it leaves a white rectangle and I want to have it transparent, as the layer below has a background.
Other layers will be stacked on top and all needs to be transparent, the deleting of the circle and redrawing should not affect this.
I have tried a few other commands, but none have worked so far. clearRect gives me this white rectangle:
https://jsfiddle.net/dorien/tghgsw5g/
You have one context: .getContext('2d') returns the CanvasRenderingContext2D for the canvas. It does not create new contexts. So, when you cx2.clearRect(0,0,300,300); you're also clearing cx1 because, well, it's the same context.
If you want separate "layers" you need to create separate <canvas>'s and position them on top of each other.

HTML5 Canvas; Image clipping and multiple images

I'm fairly new to canvas and I'm having some issues.
I'm trying to accomplish one end goal: display two images in a single canvas; one image is the background, the other image is clipped to a PNG and appears over top.
I'm part way there but I've hit a wall and I don't know how to get past it.
I've create a jsFiddle for it at http://jsfiddle.net/jhp79yg9/
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var sources = {
img1: 'https://scontent-iad3-1.cdninstagram.com/hphotos-xaf1/t51.2885-15/11351598_1454928098145798_1274636321_n.jpg',
img2: 'https://igcdn-photos-c-a.akamaihd.net/hphotos-ak-xaf1/t51.2885-15/11379733_1139595366067106_273993739_n.jpg',
mask: 'http://img12.deviantart.net/65e4/i/2013/003/6/6/png_floating_terrain_by_moonglowlilly-d5qb58m.png'
};
loadImages(sources, function(images) {
context.drawImage(images.img2, 0, 0, 240, 240);
context.drawImage(images.mask, 20, 95, 500, 349);
context.globalCompositeOperation = 'source-in';
context.drawImage(images.img1, 0, 0, 540, 540);
});
<canvas id="canvas" width="540" height="540"></canvas>
In this example I've reduced the size of the first image to 140x140 from 540x540 just so I can tell if it's messing up, which it is.
What's happening is that it's not showing the second image, it's showing the 'img1' variable twice instead of showing 'img2' at all.
Can anyone offer any assistance?
I also can't tell, because it's rendering the same image, which is in the foreground and which is in the background. Help there as well would be appreciated (the clipped image should be in the foreground).
Thanks!
Another approach would be to cut out the mask from the current content and then draw the background behind the current content:
loadImages(sources, function(images) {
context.drawImage(images.img2, 0, 0, 540, 540);
context.globalCompositeOperation = 'destination-out';
context.drawImage(images.mask, 20, 95, 500, 349);
context.globalCompositeOperation = 'destination-atop';
context.drawImage(images.img1, 0, 0, 540, 540);
})
http://jsfiddle.net/jhp79yg9/6/
I couldn't clearly understand what is your requirement, but making some assumptions I hope this will give you what you need
context.drawImage(images.img2, 100, 180, 350, 350);
context.globalCompositeOperation = 'destination-atop';
context.drawImage(images.mask, 100, 180, 350, 350);
context.drawImage(images.img1, 0, 0, 540, 540);
http://jsfiddle.net/jhp79yg9/5/

Moving All Images In A Clipped Canvas

Ok so I have just looked into the Canvas element but I have hit a snag.
I found a demo that moves three images over the top of one another using arrow keys.
However, I clipped one of them and the underlying image does not move now even though the above clipped image does.
HTML:
<canvas id="parallax-canvas">
Sorry, your browser does not support HTML5 Canvas.
</canvas>
CSS:
.parallax-canvas {
width: 400px;
height: 300px;
}
JavaScript:
$(document).ready(function() {
var w = $("#parallax-canvas").width();
var h = $("#parallax-canvas").height();
var sky = new Image();
sky.src = "assets/img/sky.jpg";
var skydx = 2;
var skyx = 0;
var mountains = new Image();
mountains.src ="assets/img/mountains.png";
var mountainsdx = 10;
var mountainsx = 0;
var jeep = new Image();
jeep.src ="assets/img/jeep.png";
var jeepx = 100;
var jeepy = 210;
var jeepsx = 0;
var jeepsxWidth = 155;
var cntx = $("#parallax-canvas")[0].getContext("2d");
setInterval(draw, 10, cntx);
$(window).keydown(function(evt) {
switch (evt.keyCode) {
case 37: // Left arrow
if ((skyx + skydx) > skydx)
skyx -= skydx;
else
skyx = w;
if ((mountainsx + mountainsdx) > mountainsdx)
mountainsx -= mountainsdx;
else
mountainsx = 398;
if (jeepsx > 0)
jeepsx -= jeepsxWidth;
else
jeepsx = (jeepsxWidth*2);
break;
case 39: // Right arrow
if ((skyx + skydx) < (w - skydx))
skyx += skydx;
else
skyx = 0;
if ((mountainsx + mountainsdx) < (w - mountainsdx))
mountainsx += mountainsdx;
else
mountainsx = 0;
if (jeepsx < (jeepsxWidth*2))
jeepsx += jeepsxWidth;
else
jeepsx = 0;
break;
}
});
function draw(_cntx) {
drawRectangle(_cntx, 0, 0, w, h);
_cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
_cntx.beginPath();
_cntx.moveTo(0,300);
_cntx.lineTo(150,150);
_cntx.lineTo(300, 300);
_cntx.closePath();
_cntx.clip();
_cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
_cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
}
function drawRectangle(_cntx, x, y, w, h) {
_cntx.beginPath();
_cntx.rect(x,y,w,h);
_cntx.closePath();
_cntx.fill();
_cntx.stroke();
}
});
The part where I added the clipping is this section:
function draw(_cntx) {
drawRectangle(_cntx, 0, 0, w, h);
_cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
_cntx.beginPath();
_cntx.moveTo(0,300);
_cntx.lineTo(150,150);
_cntx.lineTo(300, 300);
_cntx.closePath();
_cntx.clip();
_cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
_cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
}
If you remove from and including the beginPath method to the clip method it will allow the three images to all move.
WHAT IS HAPPENING: The sky image does not move when pressing left and right arrow keys, the mountain and jeep are clipped but do move within the clipped region.
WHAT I WANT TO HAPPEN: The sky image to move as well as the mountain and jeep in the clipped region.
ALSO:
I would like to know how to move the clipped region with the arrow presses. What I mean is, at the moment there is a clipped region where by pressing left and right the images move but the clipped (cut out) region remains still. I would like to know (if possible) how to go about moving the clipped region with the arrow keys.
Demo being used: http://www.ibm.com/developerworks/web/library/wa-parallaxprocessing/
Unfortunately cannot setup a Fiddle as the images are not on the site so cannot be given a URL but the .zip is on the site and by copying and pasting the bottom part of code where their method is in the JavaScript file that is what I am getting.
Thanks for any help!
EDIT: Thanks Ken for your help with shortening code and efficiency. Nothing so far though has answered my initial questions of how to move the images and also the clipping positions.
You need to set a size for your canvas - don't use CSS to set the size of a canvas:
<canvas id="parallax-canvas" width=500 height=300>
Sorry, your browser does not support HTML5 Canvas.
</canvas>
Likewise you need to read the proper size from the canvas:
$(document).ready(function() {
var canvas = $("#parallax-canvas")[0];
var w = canvas.width;
var h = canvas.height;
...
The for each time you hit the cursor keys you need to redraw everything. The canvas doesn't know what is drawn into it - it's just a bunch of pixels so we need to provide the logic for updates ourselves.
Update seem as I missed you're doing the redraw from a setInterval loop so this doesn not apply so much, but I let the example stay as it will probably be a better approach as you only need to update when something is actually changing. A loop that redraws everything all the time will quickly drain batteries for example..
For example:
$(window).keydown(function(evt) {
switch (evt.keyCode) {
case 37: // Left arrow
if ((skyx + skydx) > skydx)
skyx -= skydx;
else
skyx = w;
if ((mountainsx + mountainsdx) > mountainsdx)
mountainsx -= mountainsdx;
else
mountainsx = 398;
if (jeepsx > 0)
jeepsx -= jeepsxWidth;
else
jeepsx = (jeepsxWidth*2);
draw(cntx );
break;
...
This you need to repeat for the other moves as well.
You will also run into problems with the way you are loading the images as loading is asynchronous. You need to handle loading by tapping into the onload handler:
var sky = new Image();
sky.onload = functionToHandleLoad;
sky.src = "assets/img/sky.jpg";
As you are loading many images you would need to count the images or use a bulk loader such as my YAIL loader.
For clipping it's important to use save/restore as currently browsers doesn't handle manual reset of clip regions that well:
function draw(_cntx) {
drawRectangle(_cntx, 0, 0, w, h);
_cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
_cntx.beginPath();
_cntx.moveTo(0,300);
_cntx.lineTo(150,150);
_cntx.lineTo(300, 300);
_cntx.closePath();
_cntx.save(); /// save current clip region
_cntx.clip();
_cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
_cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
_cntx.restore(); /// reset clip
}
At the time of answering there where no fiddle available so I haven't checked the other parts of the code, but this should be a good start I think.
This function
function drawRectangle(_cntx, x, y, w, h) {
_cntx.beginPath();
_cntx.rect(x,y,w,h);
_cntx.closePath();
_cntx.fill();
_cntx.stroke();
}
can be simplified to:
function drawRectangle(_cntx, x, y, w, h) {
_cntx.fillRect(x, y, w, h);
_cntx.strokeRect(x, y, w, h);
}
No need to close path on a rect and beginPath is not needed with fillRect/strokeRect.

Categories