Trying to learn javascript canvas, but having a difficult time figuring out which of these is preferred:
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.rect(10, 10, 100, 100);
context.rect(50, 50, 100, 100);
context.stroke();
</script>
or
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(10, 10, 100, 100);
context.rect(50, 50, 100, 100);
context.stroke();
</script>
or
<script>
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.beginPath();
context.rect(10, 10, 100, 100);
context.stroke();
context.beginPath();
context.rect(50, 50, 100, 100);
context.stroke();
</script>
It seems to not matter which one I choose, two triangles will be drawn anyhoo. It is said that everytime beginPath() is called, the previous subpath made gets erased. But what about when to beginPaths are used, like in the last code snippet. Doesn't the last subpath need to be erased?
I guess that the concept of paths seems lost on me; everywhere I read that it's "like drawing with a pencil and then inking the lines". Great! But none seem to explain why this is. There's a strokeRect, why not just have a line that gets drawn immediately? Does it have to do with optimalization?
beginPath resets the current path in the context, if you've made any settings/adding paths in the context, beginPath will reset them. In this example:
context.beginPath();
context.rect(10, 10, 100, 100);
context.beginPath(); // Resets path
context.rect(50, 50, 100, 100);
context.stroke();
since you reset the path without calling stroke, you'll only get one rectangle. It has nothing to do with clearing the screen, just clearing whatever is in the memory of the context, so to speak.
http://jsbin.com/getejuxeva/edit
If you don't quite get what it's good for, compare these two:
http://jsbin.com/kojojofixa/1/edit
and
http://jsbin.com/gevacefumo/1/edit
Related
I am working on an app which uses canvas. I draw some shapes, one over another which can be filled with colours or images and overlap each other. I use clip() to clip images to fit shape, but when I change globalCompositeOperation to multiply it makes clip() stop working. I've created a simple example to present what my problem is.
Please try to open it in Google Chrome and in Mozilla Firefox. While in Chrome image can be clipped then set as a multiply of lower layer, in Mozilla after applying multiply, clipping stops working. Any ideas to solve this issue?
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
ctx.fillStyle="red"
ctx.fillRect(0,0,100,100)
// Save the state, so we can undo the clipping
ctx.save();
// Create a shape, of some sort
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 30);
ctx.lineTo(180, 10);
ctx.lineTo(200, 60);
ctx.arcTo(180, 70, 120, 0, 10);
ctx.lineTo(200, 180);
ctx.lineTo(100, 150);
ctx.lineTo(70, 180);
ctx.lineTo(20, 130);
ctx.lineTo(50, 70);
ctx.closePath();
// Clip to the current path
ctx.clip();
ctx.globalCompositeOperation="multiply";
ctx.drawImage(img, 0, 0);
// Undo the clipping
ctx.restore();
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="200" height="158"></canvas>
This sounds like a known issue on Windows version of FireFox...
Only FF devs could have provided a real fix, but since the bug has been reported at least two years ago, I wouldn't expect it to be fixed anytime soon.
Now, to workaround the issue, you could draw in two steps:
on a first offscreen canvas do the clipping,
then do the blending on the main canvas using the now clipped one.
This may incur a little memory overhead, but having a second canvas ready might also come handy if you do a lot of compositing/blending.
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// create an off-screen copy of the context
var ctx2 = canvas.cloneNode().getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
// Do the clipping on the offscreen canvas
ctx2.save();
// Create a shape, of some sort
ctx2.beginPath();
ctx2.moveTo(10, 10);
ctx2.lineTo(100, 30);
ctx2.lineTo(180, 10);
ctx2.lineTo(200, 60);
ctx2.arcTo(180, 70, 120, 0, 10);
ctx2.lineTo(200, 180);
ctx2.lineTo(100, 150);
ctx2.lineTo(70, 180);
ctx2.lineTo(20, 130);
ctx2.lineTo(50, 70);
ctx2.closePath();
ctx2.clip();
// draw the image
ctx2.drawImage(img, 0, 0);
ctx2.restore();
// Now go back on the main canvas
ctx.fillStyle = "red";
ctx.fillRect(0,0,100,100);
// do the blending with our now clipped image
ctx.globalCompositeOperation="multiply";
ctx.drawImage(ctx2.canvas,0,0);
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="200" height="158"></canvas>
But for this exact case, i.e multiply blending, you can actually simply get rid of the clipping altogether on a single canvas.
Indeed, if I'm not mistaken, multiply doesn't really cares of which layer is top or bottom, the result will just be the same.
So you could simply do your compositing first, and as a final step do the blending over the composited image.
// Grab the Canvas and Drawing Context
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
// Create an image element
var img = document.createElement('IMG');
// When the image is loaded, draw it
img.onload = function () {
ctx.fillStyle="red";
// Create a shape, of some sort
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 30);
ctx.lineTo(180, 10);
ctx.lineTo(200, 60);
ctx.arcTo(180, 70, 120, 0, 10);
ctx.lineTo(200, 180);
ctx.lineTo(100, 150);
ctx.lineTo(70, 180);
ctx.lineTo(20, 130);
ctx.lineTo(50, 70);
ctx.closePath();
// fill the current path
ctx.fill();
// draw only where our previous shape was drawn
ctx.globalCompositeOperation="source-atop";
ctx.drawImage(img, 0, 0);
// multiply blending should work the same in two directions
ctx.globalCompositeOperation="multiply";
ctx.fillRect(0,0,100,100);
}
// Specify the src to load the image
img.src = "http://i.imgur.com/gwlPu.jpg";
body {
background: #CEF;
}
<canvas id="c" width="200" height="158"></canvas>
And of course, you could very well get rid of clipping thanks to compositing and use a second offscreen canvas. (Probably my personal favorite).
I was working against a financial library that requires me to provide realtime updates to a line chart in canvas. To optimize the process of updating the chart, I thought of just updated the latest data-point rather than clearing and re-drawing the entire canvas.
When re-rendering only the latest datapoint frequently, I'm noticing that the line is not clear(there's a spot in the image).
Here's how the line looks initially(no redraw)
And after a few updates of calling "partial_rerender", this is how the line looks:
Notice the "joining" of the 2 lines is visible with a darker shade.
Is there a way to achieve partial re-drawing of lines only for the latest data point & not drawing the entire line completely?
Reference code
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.lineWidth = 2;
ctx.lineJoin = "miter";
ctx.moveTo(20, 50);
ctx.lineTo(100, 50);
ctx.save();
ctx.lineTo(150, 60);
ctx.stroke();
/*Call this every second to re-draw only the latest data point*/
function partial_rerender(){
ctx.clearRect(100,50, 400,400);
ctx.restore();
ctx.lineTo(150, 60);
ctx.stroke();
}
You need to create a new path each time you render or you end up re- rendering the same content over and over.
ctx.save() and ctx.restore() push and pop from a stack, for every restore you need to have a matching save. ctx.save(), ctx.restore(), ctx.restore()the second restore does nothing as there is no matching save.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.lineWidth = 2;
ctx.lineJoin = "miter";
ctx.moveTo(20, 50);
ctx.lineTo(100, 50);
ctx.save();
ctx.lineTo(150, 60);
ctx.stroke();
// Your function should look more like this
function partial_rerender(){
ctx.lineWidth = 2;
ctx.lineJoin = "miter";
ctx.save(); // needed to remove clip
ctx.beginPath(); // removes old path ready to create a new one
ctx.rect(100,50, 400,400); // create a clip area
ctx.clip(); // activate the clip area
ctx.clearRect(100,50, 400,400);
ctx.beginPath(); // needed to draw the new path
ctx.moveTo(100,50)
ctx.lineTo(150, 60);
ctx.stroke();
ctx.restore(); // remove the clip area
}
When you draw onto the canvas you do override the necessary pixels. But the rest stays the same.
What you are trying to achieve is not possible. You have to clear the canvas (canvas.clear()) and then redraw all elements to remove these artifacts from previous draw calls and to achieve your desired result.
I have been working on a seemingly simple graphic. I wish to create circles, with a line connecting the circles, and filling the circles in with some background. I have almost got it, but this one piece is tripping me up.
I can define the canvas, create the circles, and line connecting them just fine:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = $(window).width();
canvas.height = $(window).height();
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 10;
//Create two nodes
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
//line connecting two nodes
ctx.moveTo(100, 100);
ctx.lineTo(200, 200);
ctx.stroke();
This would look like this:
What I then do is fill the circles with an image (this is why I use clip()), but using a white color fill for the sake of example demonstrates the problem as well:
//simulate filling in nodes with image, in this case solid color
ctx.clip();
ctx.fillStyle = "white";
ctx.fill();
Now I am almost there, but there are some jagged edges there that I have read is just a little "bug" in Chrome, and also I like that thick black outline on the circles. So, I want to go back over just the 2 circles and outline them. It seems no matter what I do, the context always remembers that line connecting the two, and I end up with the connector line over the top of the image after calling stroke():
//would like to just re-outline circles, not connecting line
ctx.stokeStyle = "black";
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
ctx.stroke();
What I can't figure out is how to just outline the 2 circles again after filling in the white background (loading the image)?
I think about it like drawing in layers. First I draw some lines, then I put the images in, then I draw again on top. Not sure if the html canvas is meant to be used like that. Thanks.
JSFiddle Example Here
You are forgetting to begin a new path.
Whenever you start a new shape you must use ctx.beginPath or the context will redraw all the previous paths.
BTW the jaggy circles is because you are re-rendering them, this causes the edges to get jaggies.
var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
ctx.setTransform(1,0,0,1,0,-50); // just moving everything up to be seen in snippet.
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.fillStyle = "#FAFAFF";
ctx.lineWidth = 10;
//Create two nodes
/* dont draw the two circle the first time as you are
doubling the render causing the edges to get to sharp
making them appear jaggy.
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
*/
//line connecting two nodes
ctx.moveTo(100, 100);
ctx.lineTo(200, 200);
ctx.stroke();
ctx.beginPath(); // start a new path and removes all the previous paths
//Create two nodes
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
ctx.fill();
ctx.beginPath(); // start a new path and removes all the previous paths
//Create two nodes
ctx.arc( 100, 100, 25, 0, 2*Math.PI);
ctx.moveTo(200+25, 200)
ctx.arc( 200, 200, 25, 0, 2*Math.PI);
ctx.stroke();
I am just getting started with Canvas programming and trying to build a small game. Below is a sample code that I am trying out. My intention is to:
Create a canvas.
Fill it with some background color.
Draw a circle.
Clear the canvas.
Draw another circle in different location.
Here's the code:
var canvas = document.createElement('canvas');
canvas.width= 400;
canvas.height = 400;
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
// 2. Fill background
ctx.fillStyle = 'rgb(30,0,0)';
ctx.fillRect(0,0,400,400);
// 3. Draw circle
ctx.save();
ctx.fillStyle = 'rgba(256,30,30,.8)';
ctx.arc(50,50, 20, 0, Math.PI*2, true);
ctx.fill();
ctx.restore();
// 4. Clear Canvas
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// 5. Draw another circle
ctx.save();
ctx.fillStyle = 'rgba(256,30,30,.8)';
ctx.arc(150,150, 20, 0, Math.PI*2, true);
ctx.fill();
ctx.restore();
But as you can see, only the background color gets cleared and the first circle remains as it is.
Why is the above code fails to clear the canvas completely before drawing second circle?
If you don't use beginPath before starting a new path, all draw command keeps stacking in the current path.
What's happening here is that when you fill() the second time, the first circle is still in the current path, so even if the screen was in deed cleared, there are two circles drawn with this single fill() command.
==>> use beginPath() before starting a new path.
I am in the process of updating to kineticjs 4.7.0. I am struggling with adding text to a custom shape.
Here the code:
var triangle = new Kinetic.Shape({
drawFunc: function(context) {
this.setFill('#00D2FF');
context.beginPath();
context.moveTo(200, 50);
context.lineTo(420, 80);
context.quadraticCurveTo(300, 100, 260, 170);
context.closePath();
context.fillStrokeShape(this);
this.setFill('#FFFFFF');
context.beginPath();
context.fillText('Hello World!', 200, 150);
context.closePath();
context.fillStrokeShape(this);
},
stroke: 'black',
strokeWidth: 4
});
How do I make the text a different color to the filling of the shape, so I don't need to use Kinetic.Shape and Kinetic.Text in a group?
Here the is jsfiddle http://jsfiddle.net/qQU6G/1/
Yes, it appears that KineticJS 4.7 has a more complete wrapper of canvas.context, but the fillText method does not yet respect either context.fillStyle or this.setFill.
[ Update ]
Since Kinetic's "context" is not full-featured in regards to fillText, here's a way to get the underlying context and use that to fillText with your different color.
Here's a Fiddle example: http://jsfiddle.net/m1erickson/df6Uv/
var triangle = new Kinetic.Shape({
drawFunc: function(context) {
this.setFill('#00D2FF');
context.beginPath();
context.moveTo(200, 50);
context.lineTo(420, 80);
context.quadraticCurveTo(300, 100, 260, 170);
context.closePath();
context.fillStrokeShape(this);
var ctx=this.getContext()._context;
ctx.save();
ctx.font="18px verdana";
ctx.fillStyle="#ffffff";
ctx.fillText("Hello World!",225,90);
ctx.restore();
},
stroke: 'black',
strokeWidth: 4
});
Be warned that drawFunc can be invoked more than once and not always you will get context from canvas you expect. Kinetic will use helper canvases sometimes. I run into problems using above approach, but wrapping "extra" code with simple check helped:
...
if(context.canvas._canvas.parentNode!=null){
var ctx=this.getContext()._context;
ctx.save();
ctx.font="18px verdana";
ctx.fillStyle="#ffffff";
ctx.fillText("Hello World!",225,90);
ctx.restore();
}
...