I am wanting to create a canvas scene that involves drawing lines. Literally, to give the appearance of solid lines being drawn from one x/y coordinate to anther x/y coordinate. My trouble seems to be with my use of save() and restore(). The way I understand it is, if I save() my canvas before I begin drawing, I can then call on restore() to reset my canvas back to that beginning state. In this way, I can begin my next line without a distorted canvas.
When the code below is run, the first line is drawn as intended. I then call restore() to allow me to work with a non-distorted canvas for the next line. As a result (so it seems) the second line is drawn as instructed. I again call on restore() to allow me to draw the third line from one specified set of coordinates to another. However, this third line is not starting at the coordinates given. It’s as if the canvas is still distorted from the previous line, but I can’t understand as to why. Can anyone shed some light on my dilemma? (Also, it there is an easier to way to create line drawings of this style, for the web, could you let me know?)
var canvas = document.getElementById('canvas');
var c = canvas.getContext('2d');
c.save();
var a = 0;
function callVerticalTeal() {
if(a < 200) { //draw line the length of 200px
drawVerticalTeal();
a++;
setTimeout(callVerticalTeal, 0);
}
setTimeout(callHorizontalRed, 1200);
}
function drawVerticalTeal(){
c.transform(1,0,0,1,0,1);
c.beginPath();
c.moveTo(325, 200);
c.strokeStyle = 'teal';
c.lineCap = 'round';
c.lineWidth = 10;
c.lineTo(325, 200);
c.stroke();
}
// Start the loop
setTimeout(callVerticalTeal, 0);
var b = 0;
function callHorizontalRed() {
if(b < 200) {
drawHorizontalRed();
b++;
setTimeout(callHorizontalRed, 1000);
}
c.restore();
setTimeout(callHorizontalBlack, 1200);
}
function drawHorizontalRed(){
c.restore();
c.transform(1,0,0,1,1,0);
c.beginPath();
c.moveTo(325, 200);
c.strokeStyle = 'brown';
c.lineCap = 'round';
c.lineWidth = 10;
c.lineTo(325, 200);
c.stroke();
}
var x = 0;
function callHorizontalBlack() {
if(x < 200) {
draw();
x++;
setTimeout(call, 5000);
}
setTimeout(callVerticalBlack, 1200);
}
function draw(){
c.restore();
c.transform(1,0,0,1,1,0);
c.beginPath();
c.moveTo(325, 400);
c.strokeStyle = 'black';
c.lineCap = 'round';
c.lineWidth = 10;
c.lineTo(325, 400);
c.stroke();
}
You call context.save() only once. Typically context.save() is called first in any drawing method, and context.restore() is the last call. So it is an interceptor, if you want to call it so.
function paintSomething() {
ctx.save();
// now paint something
ctx.restore(); // we now are clean again, because we have the previously saved state
}
Related
When creating an HTML canvas I was planning on making these cylinders and animating marbles moving inside them. However, when trying to do so it would just delete everything. After messing around with my code, I discovered the problem was due to the fillStyle which was a CanvasPattern from an image.
This snippet simulates exactly what I am experiencing. The rectangle draws perfectly fine, however, after 1 second, when the interval runs, it disappears and there is no arc or "marble" drawn. There are no errors in console either
With Interval (Not working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
When I get rid of the interval it would work, but when the interval is there, nothing is drawn.
I have absolutely no idea why this is happening and I cannot find anything on the internet regarding this problem. Is there a way I can animate this marble while having the image continue to mask its fillStyle??
Without Interval (Working):
let canv = document.getElementById("canvas");
let ctx = canv.getContext('2d');
let matte = new Image(canv.width, canv.height);
matte.onload = function() {
var pattern = ctx.createPattern(matte, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canv.width, canv.height);
ctx.fillStyle = pattern;
ctx.fill();
};
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
ctx.lineWidth = "5";
ctx.fillRect(0, 0, 50, 50); // This dissapears when the setInterval runs???? Marble doesn't even draw
let x = 60,
y = 20;
//var draw = setInterval(function() { // Drawing the marble
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
y += 1;
//}, 1 * 1000);
<html>
<body>
<canvas id="canvas"></canvas>
</body>
</html>
Things I've tried:
Got rid of beginPath and closePath, doesn't make anything disappear but doesn't display arc
Recreating pattern inside the interval
Making the fillstyle a colour for everything (Works)
Making the fillstyle of the marble a colour (Doesnt work)
EDIT: After looking some more, I believe the problem is in the globalCompositeOperation. It's what deals with the pattern intersecting the drawing. When looking at all the types, source-in is the only one that satisfies my expected result, however, it's not working in this situation weirdly.
Thank you in advance
The problem is your ctx.globalCompositeOperation instruction. Using source-in, you're explicitly telling the canvas to make anything that's a different color from the new thing you're drawing (on a per pixel basis) transparent. Since every pixel is different, everything becomes transparent and you're left with what looks like an empty canvas (even if the ImageData will show RGBA data in which the RGB channels have meaningful content, but A is 0).
Remove the globalCompositeOperation rule and you're good to go, but you should probably take some time to rewrite the logic here, so that nothing happens until your image is loaded, because your code is pretty dependent on that pattern existing: wait for the image to load, the build the pattern, assign it to the context, and then start your draw loop.
const canv = document.getElementById("canvas");
const ctx = canv.getContext('2d');
let x = 60, y = 20;
function start() {
const matte = new Image(canv.width, canv.height);
matte.addEventListener(`load`, evt =>
startDrawing(ctx.createPattern(matte, 'repeat'))
);
matte.addEventListener(`load`, evt =>
console.error(`Could not load ${matte.src}...`);
);
matte.src = "https://www.muralswallpaper.com/app/uploads/classic-red-marble-textures-plain-820x532.jpg"; // An image src
}
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
setInterval(() => {
draw();
y += 10;
}, 1 * 1000);
}
function draw() {
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// and kick everything off
start();
Although on another note, normally setInterval is not the best choice for animations: you usually want requestAnimationFrame instead, with a "wall time" check (e.g. doing things depending on the actual clock, instead of trusting intervals):
...
function startDrawing(pattern) {
ctx.strokeStyle = `red`;
ctx.fillStyle = pattern;
startAnimation();
}
let playing, lastTime;
function startAnimation() {
playing = true;
lastTime = Date.now();
requestAnimationFrame(nextFrame);
}
function stopAnimation() {
playing = false;
}
function nextFrame() {
let newTime = Date.now();
if (newTime - lastTime >= 1000) {
draw();
}
if (playing) {
lastTime = newTime;
requestAnimationFrame(nextFrame);
}
}
...
https://jsbin.com/wawecedeve/edit?js,output
I am figuring this one out an unhealthy amount of time for now and I did not found any note for this bug. I started to build a simple HTML Canvas animation in JavaScript. For now I expect the small squares to move. Here is the code (I am also using babeljs):
class Pod {
constructor(x,y) {
this.posX = x;
this.posY = y;
this.velX = getRandomNumber(-5,5);
this.velY = getRandomNumber(-5,5);
}
getPos() {
return ([this.posX,this.posY]);
}
move() {
this.posX += this.velX;
this.posY += this.velY;
}
render() {
ctx.save();
ctx.rect(this.posX,this.posY,3,3);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.restore();
}
}
/*the classes end here*/
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var elementsNum = 10;
const stack = new Array(elementsNum);
for(var i = 0; i < elementsNum; i++) {
stack[i] = new Pod(getRandomNumber(0,500),getRandomNumber(0,500));
}
function run() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx = canvas.getContext('2d');
for(var i = 0; i < elementsNum; i++) {
stack[i].move();
stack[i].render();
}
//window.requestAnimationFrame(run);
}
/*helper functions*/
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
After running a cycle of the function run(), the small squares (I called them Pods) are rendered. Next cycle starts with clearing the canvas with ctx.clearRect... I am resetting the context and start moving and then drawing the Pods from the stack. When I draw the first Pod, it will draw all of them and also the previous frame.
Here is the codepen for this particular script: http://codepen.io/erikputz/pen/YNNXqX
(I knowingly commented the requestAnimationFrame, so you need to use the console to call the run() function)
Thank you forward for your help.
http://codepen.io/zfrisch/pen/bgazyO
This should solve your issue:
render() {
ctx.beginPath();
ctx.rect(this.posX,this.posY,3,3);
ctx.fillStyle = "#ffffff";
ctx.fill();
ctx.closePath();
}
With canvas you need to identify individual shapes through code by using the beginPath and closePath methods. In certain methods this is innate, like in fillRect. Hence the above code could be simplified even more to:
render() {
ctx.fillStyle = "#ffffff";
ctx.fillRect(this.posX,this.posY,3,3);
}
When you're just declaring a shape (rect) you need to specify when the path begins and when it is closed, otherwise it will most likely cause issues like the shape-bleeding you had in your original code.
Also, as a tip, you don't need to save state.save() / .restore() unless you're translating/scaling/rotating/or moving on the canvas element. Filling shapes doesn't apply.
I want to make a simple page that grows circles from its center ad infinitum. I'm almost there, but I can't figure out how to repeatedly grow them (resetting the radius i to 0 at a certain interval and calling the function again). I assume it will require a closure and some recursion, but I can't figure it out.
// Initialize canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.getElementsByTagName('body')[0].appendChild(canvas);
// Grow a circle
var i = 0;
var draw = function() {
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, i, 0, 2 * Math.PI);
ctx.fill();
i += 4;
window.requestAnimationFrame(draw);
}
draw();
Two things I'd do...
First, modify your draw function so that if the circle gets to a certain size, the i variable is reset back to zero. That starts the circle over again.
Second, add a setInterval timer to call your draw function at some time interval. See http://www.w3schools.com/js/js_timing.asp for details.
This setup will cause draw() to be called regularly, and the reset of i to zero makes it repeat.
So this did indeed require a closure. We wrap the initial function in a closure, and call it's wrapper function, which reinitializes I every time when called. draw() grows a single circle, and drawIt()() starts a new circle.
var drawIt = function(color) {
var i = 0;
return function draw() {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, i, 0, 2 * Math.PI);
ctx.fill();
i+=1*growthFactor;
// Growing circles until they are huge
if (i < canvas.width) {
window.requestAnimationFrame(draw);
if (i === spacing) {
circles++
drawIt(nextColor())();
}
}
}
};
drawIt(nextColor())();
})();
I am a beginner in canvas and I got stuck in creating multiple images in canvas. I created a ball which bounces inside canvas. Now I want to create a second ball when I click somewhere in the canvas. If my question is too dump please spare me. Here is the code.
The animate function gets executed onpage load:
function animate()
{
// do something
setInterval(function(){drawBall(ball_props,color,null);},10);
}
function drawBall(ball_props,color)
{
var c = document.getElementById("bouncy_ball");
var context = c.getContext("2d");
context.clearRect(0,0,c.width,c.height);
trajactory(ball_props);
context.beginPath();
context.arc(ball_props.center_x,ball_props.center_y,radius,0,Math.PI*2,false);
context.stroke();
context.fillStyle = color;
context.fill();
context.lineWidth = "2";
context.lineCap = "round";
context.strokeStyle = "black";
context.stroke();
boundaryConditions(ball_props);
accelerator(ball_props);
};
This helped me in creating the first ball in my canvas animation. Now i have a click function which is supposed to trigger the second ball but instead its deleting the first ball and creating a new one.
function onclickingCanvas()
{
setInterval(function(){drawBall(ball2_props,color2);},10);
}
Could you please help me fix this.Thanks in advance.
This line means that you clear your canvas each time you call "drawball" function :
context.clearRect(0,0,c.width,c.height);
So if you want to draw a second ball, you should call "drawball" once but with an array containing all the balls... something like that :
var ball1 = {
ball_props: ball_props,
color: color
}, ball2 = {
ball_props: ball2_props,
color: color2
};
var balls = [ball1];
function animate(){
setInterval(function(){drawBall(balls);},10);
}
function drawBall(balls)
{
var c = document.getElementById("bouncy_ball");
var context = c.getContext("2d");
context.clearRect(0,0,c.width,c.height);
for(var i=0; i<balls.length; i++){
trajactory(balls[i].ball_props);
context.beginPath();
context.arc(balls[i].ball_props.center_x,balls[i].ball_props.center_y,radius,0,Math.PI*2,false);
context.stroke();
context.fillStyle = balls[i].color;
context.fill();
context.lineWidth = "2";
context.lineCap = "round";
context.strokeStyle = "black";
context.stroke();
boundaryConditions(balls[i].ball_props);
accelerator(balls[i].ball_props);
}
};
function onclickingCanvas(){
balls.push(ball2);
}
Haven't tested it... but well you have the idea now.
I'm trying to make a website where an image is drawn on Canvas, then later the user is able to press a button to ctx.fill() certain parts of it with color. I'm running into issues where I can only ctx.fill() the most recently created shape which often isn't the shape I want.
Here's an example. In this code (live at http://build.rivingtondesignhouse.com/piol/test/) I'm trying to draw the first rectangle, then save() it on the stack, then draw the second rectangle (and don't save it), then when my fill() function is called I want to restore() the first rectangle and ctx.fill() it with a different pattern. It doesn't work!
In practice, I'm actually trying to fill the gray part of this complex shape with any color the user chooses AFTER the image has been drawn, but I think the technique is the same. (http://build.rivingtondesignhouse.com/piol/test/justTop.html)
Thanks in advance for any help!!!
Here's the code:
<script type="text/javascript">
var canvas;
var ctx;
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
draw();
}
function draw() {
ctx.fillStyle = '#FA6900';
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(0,0,15,150);
ctx.save();
ctx.fillStyle = '#E0E4CD';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur = 4;
ctx.shadowColor = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(30,0,30,150);
}
function fill(){
var image = new Image();
image.src = "http://www.html5canvastutorials.com/demos/assets/wood-pattern.png";
image.onload = drawPattern;
function drawPattern() {
ctx.restore();
ctx.fillStyle = ctx.createPattern(image, "repeat");
ctx.fill();
}
}
init();
There are a few misunderstands that we need to clear up before I can answer the question.
save() and restore() do not save and restore the canvas bitmap. Instead they save and restore all properties that are set on the canvas context and that's all!
For example
ctx.fillStyle = 'red';
ctx.save(); // save the fact that the fillstyle is red
ctx.fillStyle = 'blue'; // change the fillstyle
ctx.fillRect(0,0,5,5); // draws a blue rectangle
ctx.restore(); // restores the old context state, so the fillStyle is back to red
ctx.fillRect(10,0,5,5); // draws a red rectangle // draws a red rectangle
See that code live here.
So you aren't saving a rectangle (or anything drawn) by calling save(). The only way you you can save the bitmap is by drawing it (or part of it) to another canvas (using anotherCanvasContext.drawImage(canvasIWantToSave, 0, 0)) or by saving enough information that you can redraw the entire scene with the appropriate changes.
Here is an example of one way you could re-structure your code so that it does what you want: http://jsfiddle.net/xwqXb/