So I'm trying to optimize my code by only accessing the DOM's canvas properties when the window is resized but I'm getting this weird problem. Why does the resizing prevent this? P.S. not sure what to include I have 2k lines of code right now.
I'm using
document.getElementById('mycanvas').getContext("2d").clearRect(-2000, -2000, 2000, 2000)
With this in my render loop:
document.getElementById('mycanvas').getContext("2d").height = window.innerHeight
Without it:
Setting either width or height properties of your HTMLCanvasElement will reset all the properties of your context to their default. It will also remove all clipping areas, and more interestingly in your case, it will reset the transformation matrix, and set a new pixel data (all transparent-black). And this is slow, so you are right removing it from your code.
Your call to ctx.clearRect sets the x and y coords to -2000, this means that with a default matrix transform, you are clearing non-existing pixels.
Setting your HTMLCanvasElement.height was what cleared your canvas previously.
We can also see on your second screenshot that you are drawing the grid slightly more on the bottom-right every time, this indicates that you are probably modifying the context matrix (e.g ctx.translate(x, y)).
Now that you don't reset it with the height setting, you need to do it explicitly. This can be done with ctx.setTransform(1,0,0,1,0,1).
So a basic start for a drawing function would look like
function draw() {
// clear
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset the transform matrix (fast)
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear all pixels (fast)
//... start drawing ...
Now, there might be other properties that you assumed where reset to their defaults while they're not, e.g fillStyle etc. Most of them can be simply set to what you want when needed.
If you are using ctx.clip() however (but looking at the screenshot, it seems you're not), you'd need to save() and restore() the context's state to be able to remove the clipping area (bad API design...).
Related
There's a bunch of questions on panning a background image in a canvas (i.e. to simulate a 'camera' in a game with the character in the center) - and most answers suggest using the canvas' translate method.
But since you have to re-draw the image in each frame anyway, why not just clip it? Does it matter in terms of efficiency?
In particular, is panning like this a bad idea? (This example shows a simplified pan of the camera moving in one direction)
let drawing = new Image();
drawing.src = "img_src";
drawing.onload = function () {
ctx.drawImage(drawing, 0, 0);
let pos = 0
setInterval(() => {
pos += 1
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.drawImage(drawing, -pos, 0 ); // "pans" the image to the left using the option `drawImage` parameters `sx` and `sy`
}, 33);
};
Example result: fiddle
Thanks in advance!
The main advantage of using the transform matrix to control your camera is that you don't have to update all the elements in your world, you just move the world instead.
So yes, if you are willing to move only a single element (be it the background like in your case), moving only that one element might be a better choice.
But if you need several layers of elements to all move relatively to the camera, then using the transformation matrix is a better choice.
As for the perfs, I didn't ran any benchmarks on this, but I'd suspect it's exactly the same, though beware when messing with the cropping features of drawImage, at least Safari doesn't handle cropping from outside of a source canvas correctly.
I am working with clip() in canvas.
I have made a region to clip(), as specified below
this.svgRenderer.ctx.rect(positionX, positionY, Width, Height);
this.svgRenderer.ctx.clip();
After few drawing on the same canvas, I am trying to remove the clip for that region by using save() and restore().
But I am making mistakes and can't get that.
So suggest any other way to remove the clip for the specified region without using save() and restore()
.clip is a permanent context state change.
It can only be removed by wrapping it in .save and .restore.
ctx.save();
ctx.clip(); // assuming the clipping path was already declared
// draw whatever needs to be clipped
ctx.restore(); // reset the clipping region
// (and any other attributes that have been modified since .save())
Changing the canvas element width will clear the context state (and remove clipping) but will also erase the existing drawings.
I am trying to achieve grass like bend on a straight line created by using Cubic bezier curve.I have created a straight line using Bezier curve and now i want to bend it from top.But the problem I am facing is I am not able to do it dynamically.The moment I use mousemove for creating the bend,series of curves appear giving the canvas a paint like look which is somthing I dont want.I just want a single curved line.My question is how do I clear the previous lines that have been drawn and restore the latest bended curve??
My CODE:here is my code if you want to have a look at it
Create two canvases stacked on top of each other:
The first one containing the static content, the other only the dynamic content.
This way you will only need to be concerned about clearing the area the grass was drawn in (and this is much faster too as there is no need to redraw static all the time).
Place the two canvases inside a div which have position set to relative, then place the canvases using position set to absolute.
Now you can make a function to draw the static content (you will need to redraw it if the browser window gets resized etc.).
Record the area the grass is drawn within and clear only this (for each grass) in the next frame.
If this last is too complicated, just use a simple clear on the "dynamic" canvas:
ctxDyn.clear(0, 0, canvasDyn.width, canvasDyn.height);
This will also preserve transparency of this canvas, or "layer" if you like.
Use requestAnimationFrame to do the actual animation.
var isPlaying = true;
function animate() {
ctxDyn.clear(0, 0, canvasDyn.width, canvasDyn.height);
drawGrass(); //function to draw the actual grass
if (isPlaying) requestAnimationFrame(animate);
}
animate(); // start
The isPlaying is a flag you can toggle with a button on screen or something similar, to be able to stop the animation. See the link for requestAnimationFrame on how to make this cross-browser as this is still a "young" implementation.
dynCtx and dynCanvas can of course be called what you want.
You need to erase the current contents of the canvas before redrawing your updated "grass". This would involve redrawing everything else that is "static" on the canvas. For example:
function redraw() {
// Erase the current contents
ctx.fillStyle = 'white';
ctx.fill(0, 0, canvas.width, canvas.height);
// Draw the stuff that does not change from frame to frame
drawStaticContent();
// Draw the dynamic content
drawGrass();
}
In your mousemove event, update your grass curve information, then 'redraw'.
I am creating an animation in Canvas. Initially, the Canvas will have a set of images drawn on it. After certain time, say 5 seconds, an image has to be cleared from its original place and drawn at a separate place.
To clear the image, I tried using context.clearRect() to clear the portion, but no luck. Is there any other way to do this?
clearRect is the right way. Note that if you have a transformation applied, it may be clearing a different rectangle in the canvas. You can always remedy this by using:
// I have lots of transforms right now
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
// Will always clear the right space
ctx.clearRect(x, y, width, height);
ctx.restore();
// Still have my old transforms
I have a number of objects that represent an image that I want to draw onto a canvas. Each object has a X, Y, Scale property as well as a render method that gets passed a canvas object. then the canvas tag is passed to each object's render method and they all need to render themselves to the canvas.
The problem I have is that scaling is being done from the origin of the canvas and not the origin of the image. To fix this I've tried the following:
canvas.translate(-this.X, -this.Y);
canvas.scale(this.Scale, this.Scale);
canvas.translate(this.X, this.Y);
The problem with the above code is that it looks like you can only have 1 translation and the 3rd line which is supposed to move the object back to its original position becomes the translation when calling drawImage. If I remove the 3rd line I get the correct scaling origin but the image is moved to 0,0 and not rendered in the correct location.
What is the proper way to do this type of scaling?
Try:
canvas.translate(this.X, this.Y);
canvas.scale(this.Scale, this.Scale);
canvas.translate(-this.X, -this.Y);
But keep in mind, doing this you will need to do alot of ctxt.save();s and ctxt.restore();s if you got the width and height of the imagens you could simple do ctxt.drawImage(this.image, this.X, this.Y, this.Width*this.Scale, this.Height*this.Scale); since you don't need to mess with the context state stack it may be faster.
Edit:
The transformations don't affect the origin. What happen is that they must be applied in reverse order. Remember this methods are just shortcuts for stacking matrices in the transformation stack.