How to use requestAnimationFrame to animate multiple squares in a loop - javascript

I am using HTML canvas to draw multiple squares. I have 2 functions: 1) draw a square and 2) draw multiple squares inside a loop.
Now I want to animate these squares using requestAnimationFrame to draw these square one at a time. How can I achieve this. Here is a jsFiddle
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
function rect(x, y, w, h) {
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(number, size) {
for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
}
}
drawRect(10, 5);

I provided a frame limiter and tween to show you different ways of animating. The frame limiter has the steps in your example and the tween has as many steps as it takes to complete in a given amount of time.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
//requestAnim shim layer by Paul Irish
//http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
function rect(x, y, w, h, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(i, size, color) {
//for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2, color);
//}
}
var i = 0;
var incr = 1;
var i_max = 10;
var size = 5;
var fps = 10;
var delay = 1000 / fps;
var lastFrame = 0;
var animationTime = 5000
var tweenStep = i_max / ((animationTime/1000) * 60);
var j = 0;
function animateRect() {
// draw at 60fps
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawRect(i, size, "#0000FF");
// This is a frame limiter.
var currentFrame = Date.now();
if ((currentFrame - lastFrame) >= delay) {
i += incr;
if (i >= i_max) i = i_max - 2, incr = -1;
if (i < 0) i = 1, incr = 1;
lastFrame = currentFrame;
}
// this is a tween. The step is calculated for the desired time.
drawRect(j, size, "#FF0000");
j += tweenStep;
if (j >= i_max) tweenStep *= -1,j=i_max-1;
if (j < 0) tweenStep *= -1, j=0;
requestAnimFrame(animateRect);
//draw rectangle one by one here...
}
animateRect();
//drawRect(10, 5);
<canvas id="canvas" width="600" height="600"></canvas>

You can do something like
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before = new Date().getTime(), // last draw time in ms
now; // current time in ms
function animateRect() {
// get the current time to find if we should draw
now = new Date().getTime();
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
Edit:
As Blindman67 pointed out below, requestAnimFrame passes the current timestamp since the beginning of the animation to the callback. Here's how to take advantage of it:
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before; // last draw time in ms
function animateRect(now) {
if ( !before ) before = now;
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
However, this would necessitate modifying the shim the OP is using, in order to pass the current timestamp to the callback in setTimeout:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60, new Date.now());
};
})();

Related

Canvas code not updating and I'm not sure why

I'm trying to make a simple canvas program where the user clicks to create bouncing moving circles. It keeps freezing but still creates the circles without updating. I'm not sure whats going on, please help!
I'm adding each circle to an array of circles with the constructor
The setInterval loop seems to be freezing but the circles are still created even when this is happening
I'm having a hard time debugging this, any advice is greatly appreciated
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Background Test</title>
<style>
* { margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// Request animation frame -> Optimizes animation speed
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Fullscreen
c.width = window.innerWidth;
c.height = window.innerHeight;
ctx.fillStyle = 'red';
let fps = 60;
// FOR MOBILE DEVICES
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent))
fps = 29;
// Options
const background = '#333';
const circleMinSpeed = 3;
const circleMaxSpeed = 6;
const circleMinSize = 3;
const circleMaxSize = 10;
const circles = [];
let circlesCounter = 0;
const circlesTimeAlive = 20 * fps; // seconds
let i = 0;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Coordinate variables
let mouseX, mouseY, clickX, clickY;
// Tracks mouse movement
c.onmousemove = function(event)
{
mouseX = event.clientX;
mouseY = event.clientY;
};
// Tracks mouse click
c.onmousedown = function(event)
{
clickX = event.clientX;
clickY = event.clientY;
circle(clickX, clickY);
};
function draw()
{
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
// Clear canvas then draw
ctx.clearRect(0, 0, c.width, c.height);
drawBackground();
drawCos();
drawCircles();
drawTest();
}
}
// Circle constructor
function circle(x, y)
{
// Pick random color
let r = Math.floor(Math.random() * 255);
let g = Math.floor(Math.random() * 255);
let b = Math.floor(Math.random() * 255);
self.color = 'rgb(' + r + ', ' + g + ', ' + b + ')';
self.xCo = x;
self.yCo = y;
// Pick random size within ranges
self.size = circleMinSize + Math.floor(Math.random() *
(circleMaxSize - circleMinSize));
// Pick random direction & speed (spdX spdY)
self.speed = circleMinSpeed + Math.floor(Math.random() *
(circleMaxSpeed - circleMinSpeed));
self.spdX = self.speed * (Math.random() * 2) - 1; // picks -1 to 1
self.spdY = self.speed * (Math.random() * 2) - 1;
self.draw = function()
{
ctx.beginPath();
ctx.arc(self.xCo, self.yCo, self.size, 0, 2*Math.PI);
ctx.fillStyle = self.color;
ctx.fill();
};
circles[circlesCounter++] = self;
}
// Draw the background
function drawBackground()
{
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
function drawCircles()
{
for (let i = 0; i < circles.length; i++)
circles[i].draw();
}
function drawTest()
{
ctx.fillStyle = 'red';
ctx.fillRect(i++, i, 5, 5);
}
function drawCos()
{
ctx.fillStyle = 'white';
ctx.fillText("X: " + mouseX + " Y:" + mouseY, 10, 10, 200);
}
// Main loop
setInterval(function()
{
// Loop through circles and move them
for (let i = 0; i < circles.length; i++)
{
if (circle[i])
{
// Check left and right bounce
if (circle[i].xCo <= 0 || circle[i].xCo >= c.width)
circle[i].spdX = -circle[i].spdX;
circle[i].xCo += circle[i].spdX;
// Check left and right bounce
if (circle[i].yCo <= 0 || circle[i].yCo >= c.height)
circle[i].spdY = -circle[i].spdY;
circle[i].yCo += circle[i].spdY;
}
}
// Draw Everything
draw();
}, interval);
</script>
</body>
</html>
This code:
self.draw = function()
{
ctx.beginPath();
ctx.arc(self.xCo, self.yCo, self.size, 0, 2*Math.PI);
ctx.fillStyle = self.color;
ctx.fill();
};
Is overriding this function:
function draw()
{
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
// Clear canvas then draw
ctx.clearRect(0, 0, c.width, c.height);
drawBackground();
drawCos();
drawCircles();
drawTest();
}
}
You need to rethink how you want to draw your circles because you're re-drawing the black canvas every time a click event is triggered. I mean, when a click is triggered, you're applying new coordinates, color, Etc, and probably that's not what you want to do.
My suggestion is create canvas per circle and append them into a DIV.
Hope it helps!

trying to draw a sprite onto canvas but I am missing something

window.onload = function(){
theVideo();
playVideo();
Move();
Draw();
};
let objectInfo = {
canvas: null,
context: null,
// Number of sprites
numberOfFrames: 16,
image: null,
imageWidth: 128,
imageHeight: 192,
frameIndex: 0,
frameWidth: 0,
// Animation interval (ms)
msInterval: 1000,
x: 10,
y: 10,
};
const imageFile = "shaggy.png";
function Draw(){
objectInfo.context.drawImage(myImage, shift, 0, frameWidth, frameHeight, 120, 25, frameWidth, frameHeight);
}
//image setup
window.onload= function () {
// Canvas setup
objectInfo.canvas = document.querySelector("#myCanvas");
objectInfo.context = objectInfo.canvas.getContext("2d");
// Image setup
objectInfo.image = new Image();
objectInfo.image.onload = function() {
// The this object refers to image because within image onload event handler
objectInfo.imageWidth = this.width;
objectInfo.imageHeight = this.height;
// Calculate framewidth (size of each sprite)
objectInfo.frameWidth = objectInfo.imageWidth / objectInfo.numberOfFrames;
};
// Load image
objectInfo.image.src = imageFile;
};
var xPos = 0;
var yPos = 0;
//move image
function Move(e){
//right
if(e.keyCode==39){
xPos+=5;
}
//left
if(e.keyCode==37){
xPos-=5;
}
//up
if(e.keyCode==38){
yPos-=5;
}
//down
if(e.keyCode==40){
yPos+=5;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Sprite</title>
<meta charset="utf-8">
<meta name="author" content="Peyton">
<meta name="description" content="115">
<link rel= 'stylesheet' href="p4.css">
<script src="p4.js"> </script>
<style>canvas { border: 1px solid black; }</style>
<body>
<canvas width= "1300" height= "600" id= "myCanvas">
<video id="video" controls >
<source src="ScoobyDooV.mp4"/>
<source src="ScoobyDooV.ogv"/>
</video>
</canvas>
</body>
</html>
<nav>
</nav>
<main id="#wrapper"><br>
</main>
</body>
</html>
I'm really new to coding and am not sure what I am missing to call my sprite and draw the first image onto the canvas. I later have to call each measurement of my sprite and assign to a function keydown event to make it look like its walking each direction so if I could get any guidance on that too that would be great.
It looks like you're calling Draw before the image is loaded. Try placing the Draw() call within the image.onload method.
You are defining window.onload twice, only one out of your two callbacks will be executed
Your code is completely not suited to the task you are attempting. Animations require regular rendering. There is a huge amount of missing code in your example so i can really solve any problems directly.
So just thought I would give an example of how to load, animate, and render sprites sheets.
Sprite sheets
There are many ways to handle sprite sheets, though I find that using a standard method for all sprite sheets makes life easy.
Some sprite sheets have a regular layout and evenly spaced sprites, other sprite sheets, have been packed together to conserve pixels and memory.
Each sprite has a location on the sheet, the top left corner and the size as width and height.
You can attach an array of these locations to an image
For example the next function creates sprites for a regular layout (like image in your question)
function createSprites(width, height, columns, rows, image) {
const sprites = [];
var w = width / columns;
var h = height / rows;
var ix, iy;
for (iy = 0; iy < rows; iy++) {
for (ix = 0; ix < columns; ix++) {
const x = ix * w;
const y = iy * h;
sprites.push({ x, y, w, h });
}
}
image.sprites = sprites;
}
The array is added to the img so you don't have to add additional management
You can then draw the sprite by creating a custom draw function.
function drawSprite(img, sprIndex, x, y) {
const spr = img.sprites[sprIndex];
ctx.drawImage(img,
spr.x, spr.y, spr.w, spr.h, // location on sprite sheet
x , y , // location on canvas
spr.w, spr.h, // size on canvas;
);
}
You pass the sprite sheet image, the sprite index in the sprite array, and the location you want to draw the sprite.
Easy as
Because you likely know the size of the sprite sheet, and the location of the sprites you don't have to wait for the image to load to attach the sprite data.
const ctx = canvas.getContext("2d");
const spriteSheet = new Image;
spriteSheet.src = "https://i.stack.imgur.com/hOrC1.png";
// The image size is known so you dont have to wait for it to load
createSprites(128, 192, 4, 4, spriteSheet); // add a array of sprite locations
// It is important that the sprite sizes are integers
// width must be divisible by columns and height by rows
function createSprites(width, height, columns, rows, image) {
const sprites = [];
var w = width / columns;
var h = height / rows;
var ix, iy;
for (iy = 0; iy < rows; iy++) {
for (ix = 0; ix < columns; ix++) {
const x = ix * w;
const y = iy * h;
sprites.push({ x, y, w, h });
}
}
image.sprites = sprites;
}
function drawSprite(img, sprIndex, x, y) {
const spr = img.sprites[sprIndex];
ctx.drawImage(img,
spr.x, spr.y, spr.w, spr.h, // location on sprite sheet
x , y , // location on canvas
spr.w, spr.h, // size on canvas;
);
}
const walkerInfo = {
framesPerDir: 4,
movements: [{x: 0,y: 3 },{ x: -5, y: 0 }, { x: 5, y: 0 }, { x: 0, y: -3 } ],
}
const walker = {
dir: 0, // 0,1,2,3
time: 0, // time in Frames
rate: 0, // steps per frame
x: 0, // position
y: 0, //
update() {
this.time += 1;
// only move when sprite frame changes
if ((this.time % this.rate) === 0) {
this.x += walkerInfo.movements[this.dir].x;
this.y += walkerInfo.movements[this.dir].y;
if(this.x < -128 || this.x > canvas.width ||
this.y < -192 || this.y > canvas.height) {
this.x = randI(canvas.width);
this.y = randI(canvas.height);
this.dir = randI(4)
this.rate = randI(6, 12)
}
}
if(randI(1000) === 0){
this.dir = (this.dir + (randI(2) ? 2 : 1)) % 4;
this.rate = randI(6, 12)
}
},
draw() {
var index = this.dir * walkerInfo.framesPerDir;
index += (this.time / this.rate | 0) % walkerInfo.framesPerDir;
drawSprite(
spriteSheet, index,
this.x, this.y
);
}
}
function createWalker(x = randI(w), y = randI(h), dir = randI(4), rate = randI(6, 18)) {
return { ...walker, x, y, dir, rate, time: randI(100) };
}
const walkers = [];
// main update function
function update(timer) {
globalTime = timer;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
} else {
ctx.clearRect(0, 0, w, h);
}
if (spriteSheet.complete) { // has the image loaded
if (randI(walkers.length) === 0) { // odd 1/100 to create a walker
walkers.push(createWalker());
}
walkers.sort((a,b)=>a.y - b.y);
eachOf(walkers, walk => walk.update());
eachOf(walkers, walk => walk.draw());
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const eachOf = (array, cb) => {
var i = 0;
const len = array.length;
while (i < len && cb(array[i], i++, len) !== true);
};
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

hyperdrive effect in canvas across randomly placed circles

I'm trying to create a hyperdrive effect, like from Star Wars, where the stars have a motion trail. I've gotten as far as creating the motion trail on a single circle, it still looks like the trail is going down in the y direction and not forwards or positive in the z direction.
Also, how could I do this with (many) randomly placed circles as if they were stars?
My code is on jsfiddle (https://jsfiddle.net/5m7x5zxu/) and below:
var canvas = document.querySelector("canvas");
var context = canvas.getContext("2d");
var xPos = 180;
var yPos = 100;
var motionTrailLength = 16;
var positions = [];
function storeLastPosition(xPos, yPos) {
// push an item
positions.push({
x: xPos,
y: yPos
});
//get rid of first item
if (positions.length > motionTrailLength) {
positions.pop();
}
}
function update() {
context.clearRect(0, 0, canvas.width, canvas.height);
for (var i = positions.length-1; i > 0; i--) {
var ratio = (i - 1) / positions.length;
drawCircle(positions[i].x, positions[i].y, ratio);
}
drawCircle(xPos, yPos, "source");
var k=2;
storeLastPosition(xPos, yPos);
// update position
if (yPos > 125) {
positions.pop();
}
else{
yPos += k*1.1;
}
requestAnimationFrame(update);
}
update();
function drawCircle(x, y, r) {
if (r == "source") {
r = 1;
} else {
r*=1.1;
}
context.beginPath();
context.arc(x, y, 3, 0, 2 * Math.PI, true);
context.fillStyle = "rgba(255, 255, 255, " + parseFloat(1-r) + ")";
context.fill();
}
Canvas feedback and particles.
This type of FX can be done many ways.
You could just use a particle systems and draw stars (as lines) moving away from a central point, as the speed increase you increase the line length. When at low speed the line becomes a circle if you set ctx.lineWidth > 1 and ctx.lineCap = "round"
To add to the FX you can use render feedback as I think you have done by rendering the canvas over its self. If you render it slightly larger you get a zoom FX. If you use ctx.globalCompositeOperation = "lighter" you can increase the stars intensity as you speed up to make up for the overall loss of brightness as stars move faster.
Example
I got carried away so you will have to sift through the code to find what you need.
The particle system uses the Point object and a special array called bubbleArray to stop GC hits from janking the animation.
You can use just an ordinary array if you want. The particles are independent of the bubble array. When they have moved outside the screen they are move to a pool and used again when a new particle is needed. The update function moves them and the draw Function draws them I guess LOL
The function loop is the main loop and adds and draws particles (I have set the particle count to 400 but should handle many more)
The hyper drive is operated via the mouse button. Press for on, let go for off. (It will distort the text if it's being displayed)
The canvas feedback is set via that hyperSpeed variable, the math is a little complex. The sCurce function just limits the value to 0,1 in this case to stop alpha from going over or under 1,0. The hyperZero is just the sCurve return for 1 which is the hyper drives slowest speed.
I have pushed the feedback very close to the limit. In the first few lines of the loop function you can set the top speed if(mouse.button){ if(hyperSpeed < 1.75){ Over this value 1.75 and you will start to get bad FX, at about 2 the whole screen will just go white (I think that was where)
Just play with it and if you have questions ask in the comments.
const ctx = canvas.getContext("2d");
// very simple mouse
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
// High performance array pool using buubleArray to separate pool objects and active object.
// This is designed to eliminate GC hits involved with particle systems and
// objects that have short lifetimes but used often.
// Warning this code is not well tested.
const bubbleArray = () => {
const items = [];
var count = 0;
return {
clear(){ // warning this dereferences all locally held references and can incur Big GC hit. Use it wisely.
this.items.length = 0;
count = 0;
},
update() {
var head, tail;
head = tail = 0;
while(head < count){
if(items[head].update() === false) {head += 1 }
else{
if(tail < head){
const temp = items[head];
items[head] = items[tail];
items[tail] = temp;
}
head += 1;
tail += 1;
}
}
return count = tail;
},
createCallFunction(name, earlyExit = false){
name = name.split(" ")[0];
const keys = Object.keys(this);
if(Object.keys(this).indexOf(name) > -1){ throw new Error(`Can not create function name '${name}' as it already exists.`) }
if(!/\W/g.test(name)){
let func;
if(earlyExit){
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ if (items[i++].${name}() === true) { break } }`;
}else{
func = `var items = this.items; var count = this.getCount(); var i = 0;\nwhile(i < count){ items[i++].${name}() }`;
}
!this.items && (this.items = items);
this[name] = new Function(func);
}else{ throw new Error(`Function name '${name}' contains illegal characters. Use alpha numeric characters.`) }
},
callEach(name){var i = 0; while(i < count){ if (items[i++][name]() === true) { break } } },
each(cb) { var i = 0; while(i < count){ if (cb(items[i], i++) === true) { break } } },
next() { if (count < items.length) { return items[count ++] } },
add(item) {
if(count === items.length){
items.push(item);
count ++;
}else{
items.push(items[count]);
items[count++] = item;
}
return item;
},
getCount() { return count },
}
}
// Helpers rand float, randI random Int
// doFor iterator
// sCurve curve input -Infinity to Infinity out -1 to 1
// randHSLA creates random colour
// CImage, CImageCtx create image and image with context attached
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); }; // the ; after while loop is important don't remove
const sCurve = (v,p) => (2 / (1 + Math.pow(p,-v))) -1;
const randHSLA = (h, h1, s = 100, s1 = 100, l = 50, l1 = 50, a = 1, a1 = 1) => { return `hsla(${randI(h,h1) % 360},${randI(s,s1)}%,${randI(l,l1)}%,${rand(a,a1)})` }
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"),c.width = w,c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w,h), c.ctx = c.getContext("2d"), c);
// create image to hold text
var textImage = CImageCtx(1024, 1024);
var c = textImage.ctx;
c.fillStyle = "#FF0";
c.font = "64px arial black";
c.textAlign = "center";
c.textBaseline = "middle";
const text = "HYPER,SPEED FX,VII,,Battle of Jank,,Hold the mouse,button to increase,speed.".split(",");
text.forEach((line,i) => { c.fillText(line,512,i * 68 + 68) });
const maxLines = text.length * 68 + 68;
function starWarIntro(image,x1,y1,x2,y2,pos){
var iw = image.width;
var ih = image.height;
var hh = (x2 - x1) / (y2 - y1); // Slope of left edge
var w2 = iw / 2; // half width
var z1 = w2 - x1; // Distance (z) to first line
var z2 = (z1 / (w2 - x2)) * z1 - z1; // distance (z) between first and last line
var sk,t3,t3a,z3a,lines, z3, dd = 0, a = 0, as = 2 / (y2 - y1);
for (var y = y1; y < y2 && dd < maxLines; y++) { // for each line
t3 = ((y - y1) * hh) + x1; // get scan line top left edge
t3a = (((y+1) - y1) * hh) + x1; // get scan line bottom left edge
z3 = (z1 / (w2 - t3)) * z1; // get Z distance to top of this line
z3a = (z1 / (w2 - t3a)) * z1; // get Z distance to bottom of this line
dd = ((z3 - z1) / z2) * ih; // get y bitmap coord
a += as;
ctx.globalAlpha = a < 1 ? a : 1;
dd += pos; // kludge for this answer to make text move
// does not move text correctly
lines = ((z3a - z1) / z2) * ih-dd; // get number of lines to copy
ctx.drawImage(image, 0, dd , iw, lines, t3, y, w - t3 * 2, 1.5);
}
}
// canvas settings
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
// diagonal distance used to set point alpha (see point update)
var diag = Math.sqrt(w * w + h * h);
// If window size is changed this is called to resize the canvas
// It is not called via the resize event as that can fire to often and
// debounce makes it feel sluggish so is called from main loop.
function resizeCanvas(){
points.clear();
canvas.width = innerWidth;
canvas.height = innerHeight;
w = canvas.width;
h = canvas.height;
cw = w / 2; // center
ch = h / 2;
diag = Math.sqrt(w * w + h * h);
}
// create array of points
const points = bubbleArray();
// create optimised draw function itterator
points.createCallFunction("draw",false);
// spawns a new star
function spawnPoint(pos){
var p = points.next();
p = points.add(new Point())
if (p === undefined) { p = points.add(new Point()) }
p.reset(pos);
}
// point object represents a single star
function Point(pos){ // this function is duplicated as reset
if(pos){
this.x = pos.x;
this.y = pos.y;
this.dead = false;
}else{
this.x = 0;
this.y = 0;
this.dead = true;
}
this.alpha = 0;
var x = this.x - cw;
var y = this.y - ch;
this.dir = Math.atan2(y,x);
this.distStart = Math.sqrt(x * x + y * y);
this.speed = rand(0.01,1);
this.col = randHSLA(220,280,100,100,50,100);
this.dx = Math.cos(this.dir) * this.speed;
this.dy = Math.sin(this.dir) * this.speed;
}
Point.prototype = {
reset : Point, // resets the point
update(){ // moves point and returns false when outside
this.speed *= hyperSpeed; // increase speed the more it has moved
this.x += Math.cos(this.dir) * this.speed;
this.y += Math.sin(this.dir) * this.speed;
var x = this.x - cw;
var y = this.y - ch;
this.alpha = (Math.sqrt(x * x + y * y) - this.distStart) / (diag * 0.5 - this.distStart);
if(this.alpha > 1 || this.x < 0 || this.y < 0 || this.x > w || this.h > h){
this.dead = true;
}
return !this.dead;
},
draw(){ // draws the point
ctx.strokeStyle = this.col;
ctx.globalAlpha = 0.25 + this.alpha *0.75;
ctx.beginPath();
ctx.lineTo(this.x - this.dx * this.speed, this.y - this.dy * this.speed);
ctx.lineTo(this.x, this.y);
ctx.stroke();
}
}
const maxStarCount = 400;
const p = {x : 0, y : 0};
var hyperSpeed = 1.001;
const alphaZero = sCurve(1,2);
var startTime;
function loop(time){
if(startTime === undefined){
startTime = time;
}
if(w !== innerWidth || h !== innerHeight){
resizeCanvas();
}
// if mouse down then go to hyper speed
if(mouse.button){
if(hyperSpeed < 1.75){
hyperSpeed += 0.01;
}
}else{
if(hyperSpeed > 1.01){
hyperSpeed -= 0.01;
}else if(hyperSpeed > 1.001){
hyperSpeed -= 0.001;
}
}
var hs = sCurve(hyperSpeed,2);
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,0,0); // reset transform
//==============================================================
// UPDATE the line below could be the problem. Remove it and try
// what is under that
//==============================================================
//ctx.fillStyle = `rgba(0,0,0,${1-(hs-alphaZero)*2})`;
// next two lines are the replacement
ctx.fillStyle = "Black";
ctx.globalAlpha = 1-(hs-alphaZero) * 2;
//==============================================================
ctx.fillRect(0,0,w,h);
// the amount to expand canvas feedback
var sx = (hyperSpeed-1) * cw * 0.1;
var sy = (hyperSpeed-1) * ch * 0.1;
// increase alpha as speed increases
ctx.globalAlpha = (hs-alphaZero)*2;
ctx.globalCompositeOperation = "lighter";
// draws feedback twice
ctx.drawImage(canvas,-sx, -sy, w + sx*2 , h + sy*2)
ctx.drawImage(canvas,-sx/2, -sy/2, w + sx , h + sy)
ctx.globalCompositeOperation = "source-over";
// add stars if count < maxStarCount
if(points.getCount() < maxStarCount){
var cent = (hyperSpeed - 1) *0.5; // pulls stars to center as speed increases
doFor(10,()=>{
p.x = rand(cw * cent ,w - cw * cent); // random screen position
p.y = rand(ch * cent,h - ch * cent);
spawnPoint(p)
})
}
// as speed increases make lines thicker
ctx.lineWidth = 2 + hs*2;
ctx.lineCap = "round";
points.update(); // update points
points.draw(); // draw points
ctx.globalAlpha = 1;
// scroll the perspective star wars text FX
var scrollTime = (time - startTime) / 5 - 2312;
if(scrollTime < 1024){
starWarIntro(textImage,cw - h * 0.5, h * 0.2, cw - h * 3, h , scrollTime );
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Here's another simple example, based mainly on the same idea as Blindman67, concetric lines moving away from center at different velocities (the farther from center, the faster it moves..) also no recycling pool here.
"use strict"
var c = document.createElement("canvas");
document.body.append(c);
var ctx = c.getContext("2d");
var w = window.innerWidth;
var h = window.innerHeight;
var ox = w / 2;
var oy = h / 2;
c.width = w; c.height = h;
const stars = 120;
const speed = 0.5;
const trailLength = 90;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = "#fff"
ctx.fillRect(ox, oy, 1, 1);
init();
function init() {
var X = [];
var Y = [];
for(var i = 0; i < stars; i++) {
var x = Math.random() * w;
var y = Math.random() * h;
X.push( translateX(x) );
Y.push( translateY(y) );
}
drawTrails(X, Y)
}
function translateX(x) {
return x - ox;
}
function translateY(y) {
return oy - y;
}
function getDistance(x, y) {
return Math.sqrt(x * x + y * y);
}
function getLineEquation(x, y) {
return function(n) {
return y / x * n;
}
}
function drawTrails(X, Y) {
var count = 1;
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, w, h);
function anim() {
for(var i = 0; i < X.length; i++) {
var x = X[i];
var y = Y[i];
drawNextPoint(x, y, count);
}
count+= speed;
if(count < trailLength) {
window.requestAnimationFrame(anim);
}
else {
init();
}
}
anim();
}
function drawNextPoint(x, y, step) {
ctx.fillStyle = "#fff";
var f = getLineEquation(x, y);
var coef = Math.abs(x) / 100;
var dist = getDistance( x, y);
var sp = speed * dist / 100;
for(var i = 0; i < sp; i++) {
var newX = x + Math.sign(x) * (step + i) * coef;
var newY = translateY( f(newX) );
ctx.fillRect(newX + ox, newY, 1, 1);
}
}
body {
overflow: hidden;
}
canvas {
position: absolute;
left: 0;
top: 0;
}

How to do frame-based updating in a HTML canvas? [duplicate]

I am using HTML canvas to draw multiple squares. I have 2 functions: 1) draw a square and 2) draw multiple squares inside a loop.
Now I want to animate these squares using requestAnimationFrame to draw these square one at a time. How can I achieve this. Here is a jsFiddle
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
function rect(x, y, w, h) {
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(number, size) {
for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
}
}
drawRect(10, 5);
I provided a frame limiter and tween to show you different ways of animating. The frame limiter has the steps in your example and the tween has as many steps as it takes to complete in a given amount of time.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
//requestAnim shim layer by Paul Irish
//http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60);
};
})();
function rect(x, y, w, h, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.rect(x, y, w, h);
ctx.stroke();
}
function drawRect(i, size, color) {
//for (var i = 0; i <= number; i++) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2, color);
//}
}
var i = 0;
var incr = 1;
var i_max = 10;
var size = 5;
var fps = 10;
var delay = 1000 / fps;
var lastFrame = 0;
var animationTime = 5000
var tweenStep = i_max / ((animationTime/1000) * 60);
var j = 0;
function animateRect() {
// draw at 60fps
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawRect(i, size, "#0000FF");
// This is a frame limiter.
var currentFrame = Date.now();
if ((currentFrame - lastFrame) >= delay) {
i += incr;
if (i >= i_max) i = i_max - 2, incr = -1;
if (i < 0) i = 1, incr = 1;
lastFrame = currentFrame;
}
// this is a tween. The step is calculated for the desired time.
drawRect(j, size, "#FF0000");
j += tweenStep;
if (j >= i_max) tweenStep *= -1,j=i_max-1;
if (j < 0) tweenStep *= -1, j=0;
requestAnimFrame(animateRect);
//draw rectangle one by one here...
}
animateRect();
//drawRect(10, 5);
<canvas id="canvas" width="600" height="600"></canvas>
You can do something like
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before = new Date().getTime(), // last draw time in ms
now; // current time in ms
function animateRect() {
// get the current time to find if we should draw
now = new Date().getTime();
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
Edit:
As Blindman67 pointed out below, requestAnimFrame passes the current timestamp since the beginning of the animation to the callback. Here's how to take advantage of it:
var numRects = 10;
var size = 5;
var i = 1; // which rectangle we're drawing
var delay = 1000/60; // num miliseconds between frames
var before; // last draw time in ms
function animateRect(now) {
if ( !before ) before = now;
// if sufficient time passed since last draw, draw a rect
if ( now - before > delay && i <= numRects) {
rect(i * size, i * size, (i * size) * 2, (i * size) * 2);
i++;
before = now;
}
requestAnimFrame(animateRect);
}
However, this would necessitate modifying the shim the OP is using, in order to pass the current timestamp to the callback in setTimeout:
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function */ callback, /* DOMElement */ element) {
window.setTimeout(callback, 1000 / 60, new Date.now());
};
})();

Add same item onto canvas multiple times

basically what I'm trying to do is create a 'raining effect' on the canvas (doesn't exactly look like rain at the moment but I will sort that later)
This is my JavaScript so far:
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas");
cx = canvas.getContext("2d");
function rectangle (x, y, w, h) {
var randomx = Math.floor(Math.random() * canvas.width - 50);
var randomy = Math.floor(Math.random() * canvas.height - 100);
this.x = randomx || x || 0;
this.y = randomy || y || 0;
this.w = w || 0;
this.h = h || 0;
this.draw = function () {
cx.fillStyle = "blue";
cx.fillRect(this.x, this.y, this.w, this.h);
};
}
var myRectangle = new rectangle(window.randomx, window.randomy, 10, 10);
function loop () {
cx.clearRect(0, 0, canvas.width, canvas.height);
myRectangle.y++;
myRectangle.draw();
requestAnimFrame(loop);
}
loop();
Basically, it will create a 10 by 10 blue block at a random y and x point of the canvas, what I need to do is keep adding this blue block over and over onto the canvas. I tried including this for loop into the 'loop' function:
for (var i = 0; i < 20; i++) {
var myRectangle = new rectangle(window.randomx, window.randomy, 10, 10);
}
But this just keeps flashing the block at random points (I understand why, it is because it keeps overwriting the variable and placing it at a new point), would anyone be able to help me? I know it would be easier to use jQuery for this, but I'm using JavaScript only
Here is a fiddle for what it looks like at the moment (without the for loop) thanks in advance!
jsfiddle
Make an array of rectangles instead:
myRectangle[i] = new rectangle(...);
This way the previously generated ones won't get overwritten/destroyed.
Here's an example using an array. You also want to make sure you remove them when they're off the screen (note expired variable)
http://jsfiddle.net/8Jqzx/2/
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById("canvas");
cx = canvas.getContext("2d");
function rectangle (x, y, w, h) {
var randomx = Math.floor(Math.random() * canvas.width - 50);
var randomy = Math.floor(Math.random() * canvas.height - 100);
this.x = randomx || x || 0;
this.y = randomy || y || 0;
this.w = w || 0;
this.h = h || 0;
this.expired = false;
this.draw = function () {
cx.fillStyle = "blue";
cx.fillRect(this.x, this.y, this.w, this.h);
};
this.update = function () {
this.y++;
if (y > canvas.height) {
this.expired = true;
}
}
}
var rectangles = new Array();
function newRect () {
rectangles.push(new rectangle(window.randomx, window.randomy, 10, 10));
}
var timing = 0;
function loop () {
cx.clearRect(0, 0, canvas.width, canvas.height);
if (timing%10 == 0) {
newRect();
}
for (var i = 0; i < rectangles.length; i++) {
rectangles[i].update();
rectangles[i].draw();
if (rectangles[i].expired) {
rectangles.splice(i, 1);
i--;
}
}
timing++;
requestAnimFrame(loop);
}
loop();
But in this one you have them appear at the top (more rain-like): http://jsfiddle.net/8Jqzx/3/

Categories