Without any extension library, is it possible to have multiple layers in the same canvas element?
So if I do a clearRect on the top layer, it will not erase the bottom one?
Thanks.
No, however, you could layer multiple <canvas> elements on top of each other and accomplish something similar.
<div style="position: relative;">
<canvas id="layer1" width="100" height="100"
style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="layer2" width="100" height="100"
style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</div>
Draw your first layer on the layer1 canvas, and the second layer on the layer2 canvas. Then when you clearRect on the top layer, whatever's on the lower canvas will show through.
Related to this:
If you have something on your canvas and you want to draw something at the back of it - you can do it by changing the context.globalCompositeOperation setting to 'destination-over' - and then return it to 'source-over' when you're done.
var context = document.getElementById('cvs').getContext('2d');
// Draw a red square
context.fillStyle = 'red';
context.fillRect(50,50,100,100);
// Change the globalCompositeOperation to destination-over so that anything
// that is drawn on to the canvas from this point on is drawn at the back
// of what's already on the canvas
context.globalCompositeOperation = 'destination-over';
// Draw a big yellow rectangle
context.fillStyle = 'yellow';
context.fillRect(0,0,600,250);
// Now return the globalCompositeOperation to source-over and draw a
// blue rectangle
context.globalCompositeOperation = 'source-over';
// Draw a blue rectangle
context.fillStyle = 'blue';
context.fillRect(75,75,100,100);
<canvas id="cvs" />
You can create multiple canvas elements without appending them into document. These will be your layers:
Then do whatever you want with them and at the end just render their content in proper order at destination canvas using drawImage on context.
Example:
/* using canvas from DOM */
var domCanvas = document.getElementById('some-canvas');
var domContext = domCanvas.getContext('2d');
domContext.fillRect(50,50,150,50);
/* virtual canvase 1 - not appended to the DOM */
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(50,50,150,150);
/* virtual canvase 2 - not appended to the DOM */
var canvas2 = document.createElement('canvas')
var ctx2 = canvas2.getContext('2d');
ctx2.fillStyle = 'yellow';
ctx2.fillRect(50,50,100,50)
/* render virtual canvases on DOM canvas */
domContext.drawImage(canvas, 0, 0, 200, 200);
domContext.drawImage(canvas2, 0, 0, 200, 200);
And here is some codepen: https://codepen.io/anon/pen/mQWMMW
I was having this same problem too, I while multiple canvas elements with position:absolute does the job, if you want to save the output into an image, that's not going to work.
So I went ahead and did a simple layering "system" to code as if each layer had its own code, but it all gets rendered into the same element.
https://github.com/federicojacobi/layeredCanvas
I intend to add extra capabilities, but for now it will do.
You can do multiple functions and call them in order to "fake" layers.
You might also checkout http://www.concretejs.com which is a modern, lightweight, Html5 canvas framework that enables hit detection, layering, and lots of other peripheral things. You can do things like this:
var wrapper = new Concrete.Wrapper({
width: 500,
height: 300,
container: el
});
var layer1 = new Concrete.Layer();
var layer2 = new Concrete.Layer();
wrapper.add(layer1).add(layer2);
// draw stuff
layer1.sceneCanvas.context.fillStyle = 'red';
layer1.sceneCanvas.context.fillRect(0, 0, 100, 100);
// reorder layers
layer1.moveUp();
// destroy a layer
layer1.destroy();
but layer 02, will cover all drawings in layer 01. I used this to show drawing in both layers. use (background-color: transparent;) in style.
<div style="position: relative;">
<canvas id="lay01" width="500" height="500" style="position: absolute; left: 0; top: 0; z-index: 0; background-color: transparent;">
</canvas>
<canvas id="lay02" width="500" height="500" style="position: absolute; left: 0; top: 0; z-index: 1; background-color: transparent;">
</canvas>
</div>
I understand that the Q does not want to use a library, but I will offer this for others coming from Google searches. #EricRowell mentioned a good plugin, but, there is also another plugin you can try, html2canvas.
In our case we are using layered transparent PNG's with z-index as a "product builder" widget. Html2canvas worked brilliantly to boil the stack down without pushing images, nor using complexities, workarounds, and the "non-responsive" canvas itself. We were not able to do this smoothly/sane with the vanilla canvas+JS.
First use z-index on absolute divs to generate layered content within a relative positioned wrapper. Then pipe the wrapper through html2canvas to get a rendered canvas, which you may leave as-is, or output as an image so that a client may save it.
Related
I made a double pendulum with canvas.
Here is the result: https://jsfiddle.net/zndo9vh4/
As you guys can see a trace is drawn everytime the second part of the pendulum moves, and my way of doing that is by appending each coordinate to a "trace" array.
var trace = []
trace.push([x2,y2]);
And then I draw the trace by joining each coordinate with the last one:
for (let i = 1; i < trace.length; i++) {
c.moveTo(trace[i][0], trace[i][1])
c.lineTo(trace[i-1][0], trace[i-1][1])
}
I want to improve it. What i've tried so far is only adding coordinates that aren't already in the array, but it's not a big improvent because the lines are drawn every loop
var trace = []
if(trace.includes([x2, y2]) != true){
trace.push([x2,y2]);
}
The way I think could be a good improvement is by having 2 canvas (I don't know if its possible) and then draw each point but only in that canvas so I doesnt have to be redrawn. But I dont know how to implement that.
Thanks in advice
Your improvement idea is great. You can indeed have two canvases!
There are two ways to go about it.
Offscreen canvas
Using what's called an offscreen canvas (a canvas that is created in JavaScript but not added to the DOM), you can draw all the points onto it and then using drawImage (which can accept a canvas element) pass the canvas to the main context.
var offscreenCanvas = document.createElement('canvas');
var offscreenC = offscreenCanvas.getContext('2d');
offscreenCanvas.width = canvas.width;
offscreenCanvas.height = canvas.height;
// in animate function, draw points onto the offscreen canvas instead
// of the regular canvas as they are added
if(trace.includes([x2, y2]) != true){
trace.push([x2,y2]);
var i = trace.length-1;
if (i > 1) {
offscreenC.strokeStyle = 'white'
offscreenC.beginPath();
offscreenC.moveTo(trace[i][0], trace[i][1])
offscreenC.lineTo(trace[i-1][0], trace[i-1][1])
offscreenC.stroke();
}
}
c.drawImage(offscreenCanvas, 0, 0);
Layered Canvases
One of the downsides to the offscreen canvas approach is that you have to draw it to the main canvas every frame. You can further improve the approach by layering two canvases on top of one another, where the top one is just the pendulum and the bottom one the trace.
This way, you never have to redraw the offscreen canvas onto the main canvas, and save yourself some rendering time.
Updated jsfiddle
I have a graphing application that overlays several canvases. I'd like the user to be able to right-click and save image. When the user does this now, it only saves the top layer canvas (correctly), but misses the lower layers. What might a strategy be to composite the multiple canvases when the right-click occurs?
Thank you,
PT
The strategy would be, to draw all the lower canvases on the top canvas, when a user right clicks on the top canvas to save image.
You could use drawImage() method, to draw a certain canvas on another canvas.
Here is a quick example, showing how this could be done ...
var lowerCTX = document.querySelector('#lowerCanvas').getContext('2d');
var upperCTX = document.querySelector('#upperCanvas').getContext('2d');
//draw rect on lower canvas
lowerCTX.fillStyle = 'green';
lowerCTX.fillRect(20, 20, 50, 50);
//draw rect on upper canvas
upperCTX.fillStyle = 'red';
upperCTX.fillRect(130, 130, 50, 50);
//add right click event to upper canvas
upperCTX.canvas.onmousedown = function(e) {
if (e.which === 3) {
//draw lower canvas on upper canvas
upperCTX.drawImage(lowerCTX.canvas, 0, 0);
}
};
body{margin:10px 0 0 0;overflow:hidden}#canvas_wrapper{display: inline-flex}canvas{border: 1px solid #ccc}#upperCanvas{margin-left: -202px}
<div id="canvas_wrapper">
<canvas id="lowerCanvas" width="200" height="200"></canvas>
<canvas id="upperCanvas" width="200" height="200"></canvas>
</div>
I'm trying to use a canvas image as background here: http://www.cphrecmedia.dk/musikdk/stage/prelisten.php
The code is as below:
window.onload=function(){
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var imageObj = new Image();
stackBoxBlurImage('coverbgblur', 'canvas', 19, false, 2);
}
Beside the layout stuff that needs to be fixed, I need to darken the image a little bit, so the text will always be visible, also if I use a white album-cover. Can I somehow in the code above add a line or 2 that will darken the image? I know I can use CSS3, but its seems unsmart to create an extra layer of processing instead of doing it right the first time.
I'm quite new with canvas, so every kind of help is hugely appreciated!
You can make the canvas appear darker by drawing a semi-transparent dark rectangle over the image when after you draw the image
context.fillStyle = "rgba(0, 0, 0, 0.4)";
context.fillRect(0, 0, 700, 500);
Here is an example jsFiddle
You may also be able to use context.globalAlpha or context.globalCompositeOperation = "lighter"; as described in this SO post
Hi i am building a windows store app with html5 and javascript in my app i am trying to implement an eraser tool but this is problematic because if the user moves an image or another layer to where they've previously erased, they see the white drawing where they erased.
i have been trying to do the eraser tool from different ways for example i have changed the default globalCompositeOperation to "destination-out" like this code
//Here is the error.
if (clickTool[j] == "eraser") {
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(255,0,0,0.5);';
ctx.strokeStyle = 'rgba(255,0,0,0.5);';
}
else {
ctx.globalCompositeOperation = "source-over";
ctx.strokeStyle = clickColor[j];
}
but unfortunately it doesn´t work for me. i have uploaded all my code to this link:
My code
Please i would like to somebody could help me.
Thanks and i'm sorry for my speech , i'm mexican.
Use multiple layers. Have one canvas for the background image and another for the drawing; that why you never erase any of the background image.
If you need to, you can have multiple layers as they don't generally impact performance.
And of course if you can combine layers, say the last drawn squiggle to the background layer, if you deem a drawing to be "permanent".
Maintain a array of mid points. Use the globalCompositeOperation as 'destination-out' first and 'source-over' later to make a transparent eraser trail .
Following is the code that you need to use with a mouse move function
var handleMouseMove = function (event) {
midPt = new createjs.Point(oldPt.x + stage.mouseX>>1, oldPt.y+stage.mouseY>>1);
if(curTool.type=="eraser"){
var tempcanvas = document.getElementById('drawcanvas');
var tempctx=tempcanvas.getContext("2d");
tempctx.beginPath();
tempctx.globalCompositeOperation = "destination-out";
tempctx.arc(midPt.x, midPt.y, 20, 0, Math.PI * 2, false);
tempctx.fill();
tempctx.closePath();
tempctx.globalCompositeOperation = "source-over";
drawingCanvas.graphics.clear();
// keep updating the array for points
arrMidPtx.push(midPt.x);
arrMidPty.push(midPt.y);
stage.addChild(drawingCanvas);
stage.update();
}
};
I use this code to make a eraser that behaves like pen and fills up transparent color instead of white
Is it possible to use clearRect to delete part of a png image drawn to the canvas using drawImage?
I'm trying something like this and its not working:
<canvas id="canvas"></canvas>
<img id="pngimg" src="" alt="" />
[...]
canvas = document.getElementById("canvas");
pngimg = document.getElementById("pngimg");
[...]
pngimg.src = canvas.toDataURL();
context.drawImage(pngimg, 0, 0, canvas.width, canvas.height);
[...]
Then using clearRect to erase with the mouse. The erase works on the strokes that were added to the canvas using drawLine but not for images using drawImage. It must be clearRect instead of drawing a solid color because the background isn't solid. Is it possible to do this?
Where are you loading the image from?
you can't use canvas.toDataURL() if the image on the canvas originated from a different domain. see here: Why does canvas.toDataURL() throw a security exception?
In a same domain situation this should work:
Original Image: <img id="pngimg" src="http://www.domain.com/image.png" /><br/>
Canvas With Clear:
<canvas width="160" height="80" id="canvas"></canvas><br/>
Altered Image:
<img id="newImg" src="" />
and the script:
canvas = document.getElementById("canvas");
pngimg = document.getElementById("pngimg");
newImg = document.getElementById("newImg");
var context = canvas.getContext("2d");
context.drawImage(pngimg, 0, 0, canvas.width, canvas.height);
context.clearRect(125,0,35,25);
newImg.src = canvas.toDataURL("image/png");
As you haven't shared complete code I am not sure, but from the description I doubt that it is the same issue I came across earlier today. Please visit this thread.
On a canvas, difference between drawImage with png vs create a drawing using strokes?