I have a canvas with imageData inside. I would like to learn how to zoom canvas
(the fastest way)
Right now my code looks like this
// get canvas element
var canvas = document.getElementById('canvas');
var canvasWidth = 1000;
var canvasHeight = 1000;
var ctx = canvas.getContext('2d');
var imageData = ctx.getImageData(0, 0, window.innerWidth, window.innerHeight);
var buf = new ArrayBuffer(imageData.data.length);
var buf8 = new Uint8ClampedArray(buf);
var data = new Uint32Array(buf);
// little noise
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(Math.floor(Math.random() * 256) << 24) | // blue
(Math.floor(Math.random() * 256) << 12) | // green
Math.floor(Math.random() * 256) << 6; // red
}
}
imageData.data.set(buf8);
// lets start zooming
let WINDOW_WIDTH = window.innerWidth;
let WINDOW_HEIGHT = window.innerHeight;
// max and minimum level of zooming
let MAX_ZOOM = 5;
let MIN_ZOOM = 0.5;
let SCROLL_SENSITIVITY = 0.0005;
let cameraZoom = 1;
function adjustZoom(zoomAmount) {
if (zoomAmount)
{
cameraZoom += zoomAmount;
}
cameraZoom = Math.min( cameraZoom, MAX_ZOOM );
cameraZoom = Math.max( cameraZoom, MIN_ZOOM );
}
function draw() {
canvas.width = WINDOW_WIDTH;
canvas.height = WINDOW_HEIGHT;
// set new center
ctx.translate( WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2 );
// scalling
ctx.scale(cameraZoom, cameraZoom);
// clear canvas
ctx.clearRect(0,0, WINDOW_WIDTH, WINDOW_HEIGHT);
// draw pixels
ctx.putImageData(imageData, 0, 0);
// draw rectangle
ctx.fillStyle = "#FF0000";
ctx.fillRect( 100, 100, 100, 100 );
requestAnimationFrame( draw );
}
// wheel listener
canvas.addEventListener( 'wheel', (e) => adjustZoom(e.deltaY*SCROLL_SENSITIVITY));
draw();
canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 1px solid black;
image-rendering: pixelated;
}
<canvas id="canvas" width="1000px" height="1000px"></canvas>
You can see that the red square is moving while the imageData is not
How can i fix it?
Also, there is a small problem that when you scroll with the mouse, the page itself starts to move, despite the fact that I added an event handler to the canvas
UPD
maybe I need replace putImage to drawImage?
putImageData (and getImageData) methods are not affected by the current transformation of the context.
To do what you want the best is to create a second canvas, the size of your ImageData only to render the ImageData, and then to drawImage that second canvas onto the main one:
// get canvas element
var canvas = document.getElementById('canvas');
var canvasWidth = 500;
var canvasHeight = 500;
var ctx = canvas.getContext('2d');
// create a second canvas, only for the ImageData
var imageDataCanvas = document.createElement('canvas');
// <note>
// if you don't read the data, don't call getImageData
// simply create a new blank ImageData
// </note>
var imageData = ctx.createImageData(canvasWidth, canvasHeight);
// set the canvas size to the ImageData's size
imageDataCanvas.width = imageData.width;
imageDataCanvas.height = imageData.height;
var imageDataCtx = imageDataCanvas.getContext("2d");
// <note>
// don't assign a second buffer
// eats memory for nothing and .set is not that fast
// instead work directly with the one from the ImageData
// </note>
var buf = imageData.data.buffer;
var data = new Uint32Array(buf);
// made a function so it changes every frame
function makeNoise() {
// little noise
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(Math.floor(Math.random() * 256) << 24) | // blue
(Math.floor(Math.random() * 256) << 12) | // green
Math.floor(Math.random() * 256) << 6; // red
}
}
}
// no need to set anything anymore, we did modify the ArrayBuffer directly
// lets start zooming
// max and minimum level of zooming
let MAX_ZOOM = 5;
let MIN_ZOOM = 0.5;
let SCROLL_SENSITIVITY = 0.0005;
let cameraZoom = 1;
function adjustZoom(zoomAmount) {
if (zoomAmount)
{
cameraZoom += zoomAmount;
}
cameraZoom = Math.min( cameraZoom, MAX_ZOOM );
cameraZoom = Math.max( cameraZoom, MIN_ZOOM );
}
function draw() {
// update the pixels
makeNoise();
// draw the pixels on the second canvas
// <note>
// no need to clear this context
// putImageData takes care of it for us
// </note>
imageDataCtx.putImageData(imageData, 0, 0);
// <note>
// don't set the width or height of your canvas in the rendering loop
// resizing the context will reset all the properties of the context to their default
// which is already slow enough
// but it will even reassign a new drawing-buffer
// meaning GC will have to kick-in to get back the previous buffer
// (at least in Chromium browsers)
// </note>
// reset to identity matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvasWidth, canvasHeight)
// set scaling origin
ctx.translate( canvasWidth / 2, canvasHeight / 2 );
// scaling
ctx.scale(cameraZoom, cameraZoom);
// move back for drawing
ctx.translate( -canvasWidth / 2, -canvasHeight / 2 );
// <note> clear only at identity matrix </note>
// draw pixels
// avoid blur
ctx.imageSmoothingEnabled = false;
ctx.drawImage(imageDataCanvas, 0, 0);
// draw rectangle
ctx.fillStyle = "#FF0000";
ctx.fillRect( 100, 100, 100, 100 );
requestAnimationFrame( draw );
}
// wheel listener
canvas.addEventListener( 'wheel', (e) => {
e.preventDefault(); // prevent scroll
adjustZoom(e.deltaY*SCROLL_SENSITIVITY);
});
draw();
canvas {
position: absolute;
top: 0;
left: 0;
image-rendering: pixelated;
}
<canvas id="canvas" width="500" height="500"></canvas>
Now if you actually wanted to create a single static image from this ImageData, then you could create an ImageBitmap from this ImageData and use that instead:
(async() => {
// get canvas element
var canvas = document.getElementById('canvas');
var canvasWidth = 500;
var canvasHeight = 500;
var ctx = canvas.getContext('2d');
var imageData = ctx.createImageData(canvasWidth, canvasHeight);
var buf = imageData.data.buffer;
var data = new Uint32Array(buf);
// little noise
for (var y = 0; y < canvasHeight; ++y) {
for (var x = 0; x < canvasWidth; ++x) {
var value = x * y & 0xff;
data[y * canvasWidth + x] =
(255 << 24) | // alpha
(Math.floor(Math.random() * 256) << 24) | // blue
(Math.floor(Math.random() * 256) << 12) | // green
Math.floor(Math.random() * 256) << 6; // red
}
}
const img = await createImageBitmap(imageData);
// max and minimum level of zooming
let MAX_ZOOM = 5;
let MIN_ZOOM = 0.5;
let SCROLL_SENSITIVITY = 0.0005;
let cameraZoom = 1;
function adjustZoom(zoomAmount) {
if (zoomAmount)
{
cameraZoom += zoomAmount;
}
cameraZoom = Math.min( cameraZoom, MAX_ZOOM );
cameraZoom = Math.max( cameraZoom, MIN_ZOOM );
}
function draw() {
// reset to identity matrix
ctx.setTransform( 1, 0, 0, 1, 0, 0);
ctx.clearRect( 0, 0, canvasWidth, canvasHeight )
// set new center
ctx.translate( canvasWidth / 2, canvasHeight / 2 );
// scaling
ctx.scale(cameraZoom, cameraZoom);
ctx.translate( -canvasWidth / 2, -canvasHeight / 2 );
// draw pixels
// to avoid blur
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0);
// draw rectangle
ctx.fillStyle = "#FF0000";
ctx.fillRect( 100, 100, 100, 100 );
requestAnimationFrame( draw );
}
// wheel listener
canvas.addEventListener( 'wheel', (e) => {
e.preventDefault();
adjustZoom(e.deltaY*SCROLL_SENSITIVITY)
});
draw();
})()
canvas {
position: absolute;
top: 0;
left: 0;
image-rendering: pixelated;
}
<!-- createImageBitmap polyfill for Safari -->
<script src="https://cdn.jsdelivr.net/gh/Kaiido/createImageBitmap/dist/createImageBitmap.js"></script>
<canvas id="canvas" width="500" height="500"></canvas>
Related
I am new to Canvas and want to loop background Image in the below smoke effect. On searching, I have found an example that how we can loop background Image in canvas Link to looping animation so I tried integrating the looping code with the smoke effect but no success. Any help will be appreciated.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
context.drawImage(backImg, 0, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-lights';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas" ></canvas>
I just added a few lines. Hopefully you can spot them. I commented everything I added.
// Create an array to store our particles
var particles = [];
// The amount of particles to render
var particleCount = 60;
// The maximum velocity in each direction
var maxVelocity = 2;
// The target frames per second (how often do we want to update / redraw the scene)
var targetFPS = 33;
// Set the dimensions of the canvas as variables so they can be used.
var canvasWidth = window.innerWidth;
var canvasHeight = window.innerHeight;
// borders for particles on top and bottom
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
// Create an image object (only need one instance)
var imageObj = new Image();
// x position of scrolling image
var imageX = 0;
var looping = false;
var totalSeconds = 0;
// Once the image has been downloaded then set the image on all of the particles
imageObj.onload = function() {
particles.forEach(function(particle) {
particle.setImage(imageObj);
});
};
// Once the callback is arranged then set the source of the image
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
// A function to create a particle object.
function Particle(context) {
// Set the initial x and y positions
this.x = 0;
this.y = 0;
// Set the initial velocity
this.xVelocity = 0;
this.yVelocity = 0;
// Set the radius
this.radius = 5;
// Store the context which will be used to draw the particle
this.context = context;
// The function to draw the particle on the canvas.
this.draw = function() {
// If an image is set draw it
if (this.image) {
this.context.drawImage(this.image, this.x - 128, this.y - 128);
// If the image is being rendered do not draw the circle so break out of the draw function
return;
}
// Draw the circle as before, with the addition of using the position and the radius from this object.
this.context.beginPath();
this.context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false);
this.context.fillStyle = "rgba(0, 255, 255, 1)";
this.context.fill();
this.context.closePath();
};
// Update the particle.
this.update = function() {
// Update the position of the particle with the addition of the velocity.
this.x += this.xVelocity;
this.y += this.yVelocity;
// Check if has crossed the right edge
if (this.x >= canvasWidth) {
this.xVelocity = -this.xVelocity;
this.x = canvasWidth;
}
// Check if has crossed the left edge
else if (this.x <= 0) {
this.xVelocity = -this.xVelocity;
this.x = 0;
}
// Check if has crossed the bottom edge
if (this.y >= borderBottom) {
this.yVelocity = -this.yVelocity;
this.y = borderBottom;
}
// Check if has crossed the top edge
else if (this.y <= borderTop) {
this.yVelocity = -this.yVelocity;
this.y = borderTop;
}
};
// A function to set the position of the particle.
this.setPosition = function(x, y) {
this.x = x;
this.y = y;
};
// Function to set the velocity.
this.setVelocity = function(x, y) {
this.xVelocity = x;
this.yVelocity = y;
};
this.setImage = function(image) {
this.image = image;
};
}
// A function to generate a random number between 2 values
function generateRandom(min, max) {
return Math.random() * (max - min) + min;
}
// The canvas context if it is defined.
var context;
// Initialise the scene and set the context if possible
function init() {
var canvas = document.getElementById('myCanvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
if (canvas.getContext) {
// Set the context variable so it can be re-used
context = canvas.getContext('2d');
// Create the particles and set their initial positions and velocities
for (var i = 0; i < particleCount; ++i) {
var particle = new Particle(context);
// Set the position to be inside the canvas bounds
particle.setPosition(generateRandom(0, canvasWidth), generateRandom(borderTop, borderBottom));
// Set the initial velocity to be either random and either negative or positive
particle.setVelocity(generateRandom(-maxVelocity, maxVelocity), generateRandom(-maxVelocity, maxVelocity));
particles.push(particle);
context.clearRect(0, 0, canvas.width, canvas.height);
}
} else {
alert("Please use a modern browser");
}
}
// The function to draw the scene
function draw() {
// background image
context.globalAlpha = 1;
context.globalCompositeOperation = 'source-over';
// draw twice to cover wrap around
context.drawImage(backImg, imageX, 0, canvasWidth, canvasHeight);
context.drawImage(backImg, imageX + canvasWidth, 0, canvasWidth, canvasHeight);
context.fillStyle = "rgba(255,255,255, .5)";
context.fillRect(0, 0, canvasWidth, canvasHeight);
context.globalAlpha = 0.75;
context.globalCompositeOperation = 'soft-light';
// Fog layer
// Go through all of the particles and draw them.
particles.forEach(function(particle) {
particle.draw();
});
}
// Update the scene
function update() {
// incrementally change image position of background to scroll left
imageX -= maxVelocity;
if (imageX < -canvasWidth) {
imageX += canvasWidth;
}
particles.forEach(function(particle) {
particle.update();
});
}
// Initialize the scene
init();
backImg = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
// If the context is set then we can draw the scene (if not then the browser does not support canvas)
if (context) {
setInterval(function() {
// Update the scene befoe drawing
update();
// Draw the scene
draw();
}, 1000 / targetFPS);
}
<canvas id="myCanvas"></canvas>
Just to add to the answer given some additional improvements to the code structure.
Use requestAnimationFrame to call render calls.
Don't expose properties of objects if not needed.
Don't use forEach iteration in time critical code. Use for loops.
Use constants where ever possible.
Comments that state the obvious are just noise in the source code making it harder to read. Limit comment to abstracts that may not be obvious to another programmer reading the code.
eg
// If an image is set draw it
if (this.image) {
Really is that comment of any use to anyone. Comments should help not degrade the readability of code.
Also the original code tried to set the global composite operations to soft-lights this is not a know operation. I corrected it to soft-light which can on some machines, be a very slow render operation. It may pay to selected another operation for machines that are slow. This can be done by simply monitoring the render time of particles and switching operation type is too slow.
A quick rewrite of the OP's code.
const particles = [];
const particleCount = 60;
const maxVelocity = 2;
var canvasWidth = innerWidth;
var canvasHeight = innerHeight;
var borderTop = 0.01 * canvasHeight;
var borderBottom = 0.99 * canvasHeight;
var ctx;
const backgroundColor = "rgba(255,255,255, .5)";
const backgroundSpeed = -0.1;
var looping = false;
var totalSeconds = 0;
var lastTime = 0;
var frameTime = (1000 / 30) - (1000 / 120); // one quater frame short to
// allow for timing error
var imageCount = 0;
const backImg = new Image();
const imageObj = new Image();
backImg.src = 'https://image.ibb.co/cTOOdF/e2VZQY.jpg';
imageObj.src = "https://image.ibb.co/fdpeJF/Smoke.png";
backImg.onload = imageObj.onload = imageLoad;
function imageLoad(){
imageCount += 1;
if(imageCount === 2){
init();
}
}
function init() {
var canvas = myCanvas;
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext('2d');
for (var i = 0; i < particleCount; i += 1) {
particles.push(new Particle(ctx));
}
lastTime = performance.now();
requestAnimationFrame(mainLoop);
}
function mainLoop(time){
if(time-lastTime > frameTime){
lastTime = time;
update();
draw(time);
}
requestAnimationFrame(mainLoop);
}
const rand = (min, max) => Math.random() * (max - min) + min; // names are best short (short only without ambiguity)
function Particle(ctx) {
var x, y, xVel, yVel, radius, image;
const color = "rgba(0, 255, 255, 1)";
x = rand(0, canvasWidth),
y = rand(borderTop, borderBottom);
xVel = rand(-maxVelocity, maxVelocity);
yVel = rand(-maxVelocity, maxVelocity);
radius = 5;
image = imageObj;
this.draw = function () { ctx.drawImage(image, x - 128, y - 128) }
this.update = function () {
x += xVel;
y += yVel;
if (x >= canvasWidth) {
xVel = -xVel;
x = canvasWidth;
}
else if (x <= 0) {
xVel = -xVel;
x = 0;
}
if (y >= borderBottom) {
yVel = -yVel;
y = borderBottom;
}
else if (y <= borderTop) {
yVel = -yVel;
y = borderTop;
}
}
}
function draw(time) {
var i,x;
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
x = time * backgroundSpeed;
x = ((x % canvasWidth) + canvasWidth) % canvasWidth;
ctx.drawImage(backImg, x, 0, canvasWidth, canvasHeight);
ctx.drawImage(backImg, x - canvasWidth, 0, canvasWidth, canvasHeight);
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.globalAlpha = 0.75;
ctx.globalCompositeOperation = 'soft-light';
for(i = 0; i < particles.length; i += 1){
particles[i].draw();
}
}
function update() {
for(i = 0; i < particles.length; i += 1){
particles[i].update();
}
}
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=myCanvas></canvas>
WHAT? I am attempting to use canvas and JavaScript to display an animation on top of a grid which also must be drawn using JavaScript. https://jsfiddle.net/cp1wqeeg/6/
PROBLEM! To remove the previous frames of the animation I have used clearRect(). This however breaks my grid which I do not want :(
JSFiddle: https://jsfiddle.net/cp1wqeeg/5
ctx.clearRect(50, 100, width, height);
QUESTION How can I remove the previous frames of my animation without breaking the grid behind my sprite?
The common action here is to clear all and redraw everything.
But it may become cumbersome if e.g in your case, your background doesn't change.
In this case, an simple solution, is to use offscreen canvases, that will act as layers.
First you draw you grid on this off-screen canvas in the init phase.
Then in your loop, you just draw your offscreen canvas on the main context, with the drawImage method.
var canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
fov = 300,
viewDist = 5,
w = canvas.width / 2,
h = canvas.height / 2,
// here we create an offscreen canvas for the grid only
gridCtx = canvas.cloneNode().getContext('2d'),
angle = 0,
i, p1, p2,
grid = 5;
function initGrid(){
/// create vertical lines on the off-screen canvas
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
i++;
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
gridCtx.moveTo(p1[0], p1[1]);
gridCtx.lineTo(p2[0], p2[1]);
}
gridCtx.stroke();
}
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180;
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca;
rz = y * sa;
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
initGrid();
var width = 200,
height = 200,
frames = 2,
currentFrame = 0,
imageSprite = new Image()
imageSprite.src = 'https://s27.postimg.org/eg1cjz6cz/sprite.png';
var drawSprite = function(){
ctx.clearRect(0,0,canvas.width, canvas.height);
ctx.drawImage(gridCtx.canvas, 0,0); // now draw our grid canvas
ctx.drawImage(imageSprite, 0, height * currentFrame, width, height, 50, 100, width, height);
if (currentFrame == frames) {
currentFrame = 0;
} else {
currentFrame++;
}
}
setInterval(drawSprite, 500);
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #c3c3c3;"></canvas>
I am new learner of animation using HTML5 Canvas. I am struggling to create line drawing animation in a canvas with desired length of a line.
Here is the code
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight;
var x = 200;
var y = 200;
draw();
update();
function draw() {
context.beginPath();
context.moveTo(100, 100);
context.lineTo(x, y);
context.stroke();
}
function update() {
context.clearRect(0, 0, width, height);
x = x + 1;
y = y + 1;
draw();
requestAnimationFrame(update);
}
html,
body {
margin: 0px;
}
canvas {
display: block;
}
<canvas id="canvas"></canvas>
The line is growing on Canvas in the above code. But how to achieve that the 200px wide line and animate the movement in x and y direction. And the same animation with multiple lines using for loop and move them in different direction.
Check the reference image ....
Need to move each line in a different direction
Thanks in advance
Find a new reference image which i want to achieve
You need to either use transforms or a bit of trigonometry.
Transforms
For each frame:
Reset transforms and translate to center
Clear canvas
Draw line from center to the right
Rotate x angle
Repeat from step 2 until all lines are drawn
var ctx = c.getContext("2d");
var centerX = c.width>>1;
var centerY = c.height>>1;
var maxLength = Math.min(centerX, centerY); // use the shortest direction for demo
var currentLength = 0; // current length, for animation
var lenStep = 1; // "speed" of animation
function render() {
ctx.setTransform(1,0,0,1, centerX, centerY);
ctx.clearRect(-centerX, -centerY, c.width, c.height);
ctx.beginPath();
for(var angle = 0, step = 0.1; angle < Math.PI * 2; angle += step) {
ctx.moveTo(0, 0);
ctx.lineTo(currentLength, 0);
ctx.rotate(step);
}
ctx.stroke(); // stroke all at once
}
(function loop() {
render();
currentLength += lenStep;
if (currentLength < maxLength) requestAnimationFrame(loop);
})();
<canvas id=c></canvas>
You can use transformation different ways, but since you're learning I kept it simple in the above code.
Trigonometry
You can also calculate the line angles manually using trigonometry. Also here you can use different approaches, ie. if you want to use delta values, vectors or brute force using the math implicit.
For each frame:
Reset transforms and translate to center
Clear canvas
Calculate angle and direction for each line
Draw line
var ctx = c.getContext("2d");
var centerX = c.width>>1;
var centerY = c.height>>1;
var maxLength = Math.min(centerX, centerY); // use the shortest direction for demo
var currentLength = 0; // current length, for animation
var lenStep = 1; // "speed" of animation
ctx.setTransform(1,0,0,1, centerX, centerY);
function render() {
ctx.clearRect(-centerX, -centerY, c.width, c.height);
ctx.beginPath();
for(var angle = 0, step = 0.1; angle < Math.PI * 2; angle += step) {
ctx.moveTo(0, 0);
ctx.lineTo(currentLength * Math.cos(angle), currentLength * Math.sin(angle));
}
ctx.stroke(); // stroke all at once
}
(function loop() {
render();
currentLength += lenStep;
if (currentLength < maxLength) requestAnimationFrame(loop);
})();
<canvas id=c></canvas>
Bonus animation to play around with (using the same basis as above):
var ctx = c.getContext("2d", {alpha: false});
var centerX = c.width>>1;
var centerY = c.height>>1;
ctx.setTransform(1,0,0,1, centerX, centerY);
ctx.lineWidth = 2;
ctx.strokeStyle = "rgba(0,0,0,0.8)";
ctx.shadowBlur = 16;
function render(time) {
ctx.globalAlpha=0.77;
ctx.fillRect(-500, -500, 1000, 1000);
ctx.globalAlpha=1;
ctx.beginPath();
ctx.rotate(0.025);
ctx.shadowColor = "hsl(" + time*0.1 + ",100%,75%)";
ctx.shadowBlur = 16;
for(var angle = 0, step = Math.PI / ((time % 200) + 50); angle < Math.PI * 2; angle += step) {
ctx.moveTo(0, 0);
var len = 150 + 150 * Math.cos(time*0.0001618*angle*Math.tan(time*0.00025)) * Math.sin(time*0.01);
ctx.lineTo(len * Math.cos(angle), len * Math.sin(angle));
}
ctx.stroke();
ctx.globalCompositeOperation = "lighter";
ctx.shadowBlur = 0;
ctx.drawImage(ctx.canvas, -centerX, -centerY);
ctx.drawImage(ctx.canvas, -centerX, -centerY);
ctx.globalCompositeOperation = "source-over";
}
function loop(time) {
render(time);
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
body {margin:0;background:#222}
<canvas id=c width=640 height=640></canvas>
Here is what I think you are describing...
window.onload = function() {
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = 400,
height = canvas.height = 220,
xcenter = 200,
ycenter = 110,
radius = 0,
radiusmax = 100,
start_angle1 = 0,
start_angle2 = 0;
function toRadians(angle) {
return angle * (Math.PI / 180);
}
function draw(x1, y1, x2, y2) {
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}
function drawWheel(xc, yc, start_angle, count, rad) {
var inc = 360 / count;
for (var angle = start_angle; angle < start_angle + 180; angle += inc) {
var x = Math.cos(toRadians(angle)) * rad;
var y = Math.sin(toRadians(angle)) * rad;
draw(xc - x, yc - y, xc + x, yc + y);
}
}
function update() {
start_angle1 += 0.1;
start_angle2 -= 0.1;
if(radius<radiusmax) radius++;
context.clearRect(0, 0, width, height);
drawWheel(xcenter, ycenter, start_angle1, 40, radius);
drawWheel(xcenter, ycenter, start_angle2, 40, radius);
requestAnimationFrame(update);
}
update();
};
html,
body {
margin: 0px;
}
canvas {
display: block;
}
<canvas id="canvas"></canvas>
This is one that is a variable length emerging pattern. It has a length array element for each spoke in the wheel that grows at a different rate. You can play with the settings to vary the results:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
var xcenter = width/4;
var ycenter = height/2;
var radius;
var time;
if(width>height) {
radius = height*0.4;
}
else {
radius = width*0.4;
}
var start_angle1 = 0;
var start_angle2 = 0;
function toRadians (angle) {
return angle * (Math.PI / 180);
}
function draw(x1,y1,x2,y2) {
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.stroke();
}
var radmax=width;
var rads = [];
var radsinc = [];
function drawWheel(xc,yc,start_angle,count,rad) {
var inc = 360/count;
var i=0;
for(var angle=start_angle; angle < start_angle+180; angle +=inc) {
var x = Math.cos(toRadians(angle)) * rads[rad+i];
var y = Math.sin(toRadians(angle)) * rads[rad+i];
draw(xc-x,yc-y,xc+x,yc+y);
rads[rad+i] += radsinc[i];
if(rads[rad+i] > radmax) rads[rad+i] = 1;
i++;
}
}
function update() {
var now = new Date().getTime();
var dt = now - (time || now);
time = now;
start_angle1 += (dt/1000) * 10;
start_angle2 -= (dt/1000) * 10;
context.clearRect(0,0,width,height);
drawWheel(xcenter,ycenter,start_angle1,50,0);
drawWheel(xcenter,ycenter,start_angle2,50,50);
requestAnimationFrame(update);
}
function init() {
for(var i=0;i<100;i++) {
rads[i] = 0;
radsinc[i] = Math.random() * 10;
}
}
window.onload = function() {
init();
update();
};
html, body {
margin: 0px;
}
canvas {
width:100%;
height:200px;
display: block;
}
<canvas id="canvas"></canvas>
The goal is to have fireworks come up over top of an existing web page, so that you can see both the existing page, and the fireworks exploding over top of it. I successfully got them over top of the page, however, now they do not fade out. I'm left with white build up over top of web page.
I have this jsfiddle:
http://jsfiddle.net/2EQ2w/1/
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight,
mousePos = {
x: 400,
y: 300
},
// create canvas
canvas = document.createElement('canvas'),
context = canvas.getContext('2d'),
particles = [],
rockets = [],
MAX_PARTICLES = 400,
colorCode = 0;
// init
$(document).ready(function() {
document.body.insertBefore(canvas, document.body.firstChild);
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
setInterval(launch, 800);
setInterval(loop, 1000 / 50);
});
// update mouse position
$(document).mousemove(function(e) {
e.preventDefault();
mousePos = {
x: e.clientX,
y: e.clientY
};
});
// launch more rockets!!!
$(document).mousedown(function(e) {
for (var i = 0; i < 5; i++) {
launchFrom(Math.random() * SCREEN_WIDTH * 2 / 3 + SCREEN_WIDTH / 6);
}
});
function launch() {
launchFrom(mousePos.x);
}
function launchFrom(x) {
if (rockets.length < 10) {
var rocket = new Rocket(x);
rocket.explosionColor = Math.floor(Math.random() * 360 / 10) * 10;
rocket.vel.y = Math.random() * -3 - 4;
rocket.vel.x = Math.random() * 6 - 3;
rocket.size = 8;
rocket.shrink = 0.999;
rocket.gravity = 0.01;
rockets.push(rocket);
}
}
function loop() {
// update screen size
if (SCREEN_WIDTH != window.innerWidth) {
canvas.width = SCREEN_WIDTH = window.innerWidth;
}
if (SCREEN_HEIGHT != window.innerHeight) {
canvas.height = SCREEN_HEIGHT = window.innerHeight;
}
// clear canvas
context.fillStyle = "rgba(0, 0, 0, 0.001)";
context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
var existingRockets = [];
for (var i = 0; i < rockets.length; i++) {
// update and render
rockets[i].update();
rockets[i].render(context);
// calculate distance with Pythagoras
var distance = Math.sqrt(Math.pow(mousePos.x - rockets[i].pos.x, 2) + Math.pow(mousePos.y - rockets[i].pos.y, 2));
// random chance of 1% if rockets is above the middle
var randomChance = rockets[i].pos.y < (SCREEN_HEIGHT * 2 / 3) ? (Math.random() * 100 <= 1) : false;
/* Explosion rules
- 80% of screen
- going down
- close to the mouse
- 1% chance of random explosion
*/
if (rockets[i].pos.y < SCREEN_HEIGHT / 5 || rockets[i].vel.y >= 0 || distance < 50 || randomChance) {
rockets[i].explode();
} else {
existingRockets.push(rockets[i]);
}
}
rockets = existingRockets;
var existingParticles = [];
for (var i = 0; i < particles.length; i++) {
particles[i].update();
// render and save particles that can be rendered
if (particles[i].exists()) {
particles[i].render(context);
existingParticles.push(particles[i]);
}
}
// update array with existing particles - old particles should be garbage collected
particles = existingParticles;
while (particles.length > MAX_PARTICLES) {
particles.shift();
}
}
function Particle(pos) {
this.pos = {
x: pos ? pos.x : 0,
y: pos ? pos.y : 0
};
this.vel = {
x: 0,
y: 0
};
this.shrink = .97;
this.size = 2;
this.resistance = 1;
this.gravity = 0;
this.flick = false;
this.alpha = 1;
this.fade = 0;
this.color = 0;
}
Particle.prototype.update = function() {
// apply resistance
this.vel.x *= this.resistance;
this.vel.y *= this.resistance;
// gravity down
this.vel.y += this.gravity;
// update position based on speed
this.pos.x += this.vel.x;
this.pos.y += this.vel.y;
// shrink
this.size *= this.shrink;
// fade out
this.alpha -= this.fade;
};
Particle.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255,255,255," + this.alpha + ")");
gradient.addColorStop(0.8, "hsla(" + this.color + ", 100%, 50%, 0)");
gradient.addColorStop(1, "hsla(" + this.color + ", 100%, 50%, 0)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Particle.prototype.exists = function() {
return this.alpha >= 0.1 && this.size >= 1;
};
function Rocket(x) {
Particle.apply(this, [{
x: x,
y: SCREEN_HEIGHT}]);
this.explosionColor = 0;
}
Rocket.prototype = new Particle();
Rocket.prototype.constructor = Rocket;
Rocket.prototype.explode = function() {
var count = Math.random() * 10 + 80;
for (var i = 0; i < count; i++) {
var particle = new Particle(this.pos);
var angle = Math.random() * Math.PI * 2;
// emulate 3D effect by using cosine and put more particles in the middle
var speed = Math.cos(Math.random() * Math.PI / 2) * 15;
particle.vel.x = Math.cos(angle) * speed;
particle.vel.y = Math.sin(angle) * speed;
particle.size = 10;
particle.gravity = 0.2;
particle.resistance = 0.92;
particle.shrink = Math.random() * 0.05 + 0.93;
particle.flick = true;
particle.color = this.explosionColor;
particles.push(particle);
}
};
Rocket.prototype.render = function(c) {
if (!this.exists()) {
return;
}
c.save();
c.globalCompositeOperation = 'lighter';
var x = this.pos.x,
y = this.pos.y,
r = this.size / 2;
var gradient = c.createRadialGradient(x, y, 0.1, x, y, r);
gradient.addColorStop(0.1, "rgba(255, 255, 255 ,255)");
gradient.addColorStop(0.1, "rgba(0, 0, 0, 0)");
c.fillStyle = gradient;
c.beginPath();
c.arc(this.pos.x, this.pos.y, this.flick ? Math.random() * this.size / 2 + this.size / 2 : this.size, 0, Math.PI * 2, true);
c.closePath();
c.fill();
c.restore();
};
Which was built off this base:
http://jsfiddle.net/dtrooper/AceJJ/
Does anyone know how I can get these fireworks to fade out? Or get the particle to fade out after it hasn't moved for a few milliseconds?
You can definitely have this with fading trails:
http://jsfiddle.net/LgjG8/
Just set up a second off-screen canvas that has a reduced global alpha:
// create 2nd canvas
var canvas2 = document.createElement('canvas'),
context2 = canvas2.getContext('2d');
canvas2.width = canvas.width;
canvas2.height = canvas.height;
// reduce alpha of second canvas
context2.globalAlpha = 0.8;
Then instead of simply wiping the canvas clean each frame, copy the first on-screen canvas to the second. This will produce a faded copy of the visible canvas due to the lowered global alpha value. Then wipe the first canvas before copying the faded version back. Finally, just update the canvas as normal. This will produce a trail.
// produce faded copy of current canvas
context2.clearRect(0, 0, canvas2.width, canvas2.height);
context2.drawImage(canvas, 0, 0);
// redraw faded copy on original canvas
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(canvas2, 0, 0);
I didn't really look through your code so you might need to play with this a little, but you get the idea.
The fireworks use fillRect() with a low opacity to clear (fade out) old fireworks. As a result, nothing behind the canvas will show.
However, you can use clearRect() instead so that the canvas does not have a solid background. The problem with this is that the fireworks don't leave nice trails because there is no low opacity fill to fade them out.
Not optimal, but at least the fireworks are in front of the other page content. I wish there was a clearStyle you could set to low opacity but, sadly, no.
// clear canvas
//context.fillStyle = "rgba(0, 0, 0, 0.05)";
//context.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
context.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
WORKING EXAMPLE
Since you want to have the content visible, you can try to change the way the trails are generated and use the clearRect. Instead to have the trails done by the c.fill() you can make it to be done by particles, so you can view them.
In the Rocket.prototype.render you can do this:
//c.fill();
var particle = new Particle(this.pos);
particle.shrink = Math.random() * 0.05 + 0.93;
particle.size = 10;
particles.push(particle);
And the trails will be visible then.
Example
Before edited answer (not working as asker expected):
In the loop() function you have a really small alpha, making that the fireworks are not fading out.
Try to change:
context.fillStyle = "rgba(0, 0, 0, 0.001)";
to
context.fillStyle = "rgba(0, 0, 0, 0.05)";
Hope it helps!
I was trying to generate a canvas with random noise, but I couldn't afford to generate a entire canvas of random pixels at 60fps, so I ended up using a temporary canvas in memory to generate a small 64x64 tile, and then using context fill to repeat the pattern, and let the browser push those bytes to the screen, instead of using the javascript engine.
It was much faster, and I could get a solid 60fps on a iOS device even on fullscreen, but I noticed that after some minutes the fps stated to drop until it got very slow.
On this fiddle I'm not using requestAnimationFrame that should limit to 60Hz, instead I'm using a custom loop, on my macbook it starts at around 500Hz and quickly slows down to emphasize the problem.
http://jsfiddle.net/Victornpb/m42NT/2/
function loop(){
drawNoise();
}
function drawNoise(){
var context = canvas.getContext("2d");
var pattern = context.createPattern(generatePattern(), "repeat");
context.rect(0,0, canvas.width, canvas.height);
context.fillStyle = pattern;
context.fill()
}
//create a on memory canvas to generate a tile with 64x64 pixels of noise and return it
function generatePattern(){
var canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
var context = canvas.getContext("2d");
var image = context.getImageData(0, 0, canvas.width, canvas.height);
var imageData = image.data; // here we detach the pixels array from DOM
var p;
var pixels = canvas.width*canvas.height;
while(pixels--){
p = pixels*4;
imageData[p+0] = Math.random() >= 0.5 ? 255 : 0; // Red
imageData[p+1] = Math.random() >= 0.5 ? 255 : 0; // Green
imageData[p+2] = Math.random() >= 0.5 ? 255 : 0; // Blue
imageData[p+3] = 255; // Alpha
}
image.data = imageData;
context.putImageData(image, 0, 0);
return canvas;
}
You are using context.rect in your main draw function, without creating a new path (beginPath). So all your rect sub-path add, and needs a re-draw on each frame ==>> soon enough it is too slow.
==>> Either use beginPath() before using rect or use fillRect.
function drawNoise() {
var context = canvas.getContext("2d");
var pattern = context.createPattern(generatePattern(), "repeat");
context.fillStyle = pattern;
context.fillRect(0, 0, canvas.width, canvas.height);
}
Remark that you can win a great deal of time by not creating a canvas, and creating an image data on each call of generatePattern, but rather re-use the same imageData again and again.
What's more, you can only set the alpha once :
//create a on memory canvas to generate a tile with 64x64 pixels of noise and return it
var generatePattern = (function () {
var canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
var context = canvas.getContext("2d");
var image = context.getImageData(0, 0, canvas.width, canvas.height);
var imageData = image.data; // here we detach the pixels array from DOM
// set the alpha only once.
var p = 0,
pixels = canvas.width * canvas.height;
while (pixels--) {
imageData[p + 3] = 255; // Alpha
p += 4;
}
var _generatePattern = function () {
var p = 0;
var pixels = canvas.width * canvas.height;
var data = imageData;
var rnd = Math.random;
while (pixels--) {
data[p++ ] = rnd() >= 0.5 ? 255 : 0; // Red
data[p++ ] = rnd() >= 0.5 ? 255 : 0; // Green
data[p++ ] = rnd() >= 0.5 ? 255 : 0; // Blue
p++;
}
context.putImageData(image, 0, 0);
return canvas;
}
return _generatePattern;
})();
Updated fiddle is here :
http://jsfiddle.net/gamealchemist/m42NT/15/
Edit : using one call to random() just to get one random bit is an overkill : use math.random() to get a bitfield, then re-fill this bitfield when it is empty. Here i took 21 bits from Math.random(), because it has not much more significant bits. This way you use 21 times less call to this function (!!) for same result.
http://jsfiddle.net/gamealchemist/m42NT/18/
//create a on memory canvas to generate a tile with 64x64 pixels of noise and return it
var generatePattern = (function () {
var canvas = document.createElement("canvas");
canvas.width = 64;
canvas.height = 64;
var context = canvas.getContext("2d");
var image = context.getImageData(0, 0, canvas.width, canvas.height);
var imageData = image.data; // here we detach the pixels array from DOM
// set the alpha only once.
var p = 0,
pixels = canvas.width * canvas.height;
while (pixels--) {
imageData[p + 3] = 255; // Alpha
p += 4;
}
var _generatePattern = function () {
var p = 0;
var pixels = canvas.width * canvas.height;
var data = imageData;
var rnd = Math.random;
var bitsLeft = 0;
var multiplier = (1<<22)-1;
var mask = 0;
while (pixels--) {
if (!bitsLeft) {
bitsLeft=21;
mask= 0 | (Math.random()*multiplier);
}
data[p++ ] = (mask & 1) && 255 ; // Red
data[p++ ] = (mask & 2 ) && 255 ; // Green
data[p++ ] = (mask & 4) && 255; // Blue
p++;
mask>>=3;
bitsLeft-=3;
}
context.putImageData(image, 0, 0);
return canvas;
}
return _generatePattern;
})();
try window.requestAnimationFrame(yourLoopFunction); when you call your loop function.
function loop(){
drawNoise();
window.requestAnimationFrame(loop);
}
loop();