Does the animation run always with the same speed? - javascript

I have this portion of JavaScript code which draws a bar (for let's say a bar diagram):
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerHeight / 3 * 4;
canvas.height = window.innerHeight;
const xCharCnt = window.innerHeight / 3 * 4 / 48;
const yCharCnt = window.innerHeight / 34;
const foreground1 = "#AF5BEC";
let offset = 0;
const barHeight = 20;
const speed = 4;
const limit = yCharCnt * barHeight;
let requestId = window.requestAnimationFrame(render);
function render() {
requestId = window.requestAnimationFrame(render);
// Clear screen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw bar
ctx.fillStyle = foreground1;
ctx.fillRect(xCharCnt * 7, yCharCnt * 28, xCharCnt, -offset);
offset = offset + speed;
// Cancel animation
if (offset >= limit) window.cancelAnimationFrame(requestId);
}
Questions:
a) Is it the right way to do an animation?
b) Does the animation run always with the same speed, regardless the resolution of the device it's running on?

It will be framerate dependent, so it will differ between devices.
The number of callbacks is usually 60 times per second, but will generally match the display refresh rate in most web browsers as per W3C recommendation. requestAnimationFrame() calls are paused in most browsers when running in background tabs or hidden s in order to improve performance and battery life.
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
The above link also gives a suggestion on how you should code your animations so they do always run at the same speed. Translated to your code that would look like:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerHeight / 3 * 4;
canvas.height = window.innerHeight;
const xCharCnt = window.innerHeight / 3 * 4 / 48;
const yCharCnt = window.innerHeight / 34;
const foreground1 = "#AF5BEC";
let offset = 0;
const barHeight = 20;
const speed = 4;
const limit = yCharCnt * barHeight;
let requestId = window.requestAnimationFrame(render);
let previousTimestamp;
function render(timestamp) {
if(previousTimestamp === undefined) {
previousTimestmap = timestamp;
}
const delta = timestamp - previousTimestamp;
offset = Math.min(offset + (speed * delta), limit);
// Clear screen
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw bar
ctx.fillStyle = foreground1;
ctx.fillRect(xCharCnt * 7, yCharCnt * 28, xCharCnt, -offset);
// Cancel animation
requestId = window.requestAnimationFrame(render);
if (offset >= limit) window.cancelAnimationFrame(requestId);
previousTimestmap = timestamp;
}

Related

Section wise division on canvas loop

I'm using Canvas animation for image sequences on the scroll.
And this animation is working fine but I need to reduce the loading time because I'm using almost 884 images for this animation.
so I need to add section-wise division in this loop. for example, the first 100 images will upload on start and after 5 seconds next 100 images will upload. so every after 5 seconds next 100 images will upload til 884 images.
please check my code and give me suggestions or help me please. thank you in advance.
const html = document.documentElement;
const canvas = document.getElementById("hero-lightpass");
const context = canvas.getContext("2d");
const frameCount = 884;
const currentFrame = index => (
`compressed/${index.toString().padStart(9, 'web_0000')}.webp`
)
const preloadImages = () => {
for (let i = 1; i < frameCount; i++) {
const img = new Image();
img.src = currentFrame(i);
}
};
const img = new Image()
img.src = currentFrame(1);
canvas.width= window.innerWidth;
canvas.height=window.innerHeight;
img.onload=function(){
scaleToFill(this);
}
function scaleToFill(img){
var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
var x = (canvas.width / 2) - (img.width / 2) * scale;
var y = (canvas.height / 2) - (img.height / 2) * scale;
context.drawImage(img, x, y, img.width * scale, img.height * scale);
}
const updateImage = index => {
img.src = currentFrame(index);
}
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const frameIndex = Math.min(
frameCount - 1,
Math.ceil(scrollFraction * frameCount)
);
requestAnimationFrame(() => updateImage(frameIndex + 1))
});
preloadImages()
HTML
<canvas id="hero-lightpass"></canvas>

Node - Why is my gif so slow when using GifEncoder

i want to render a Gif with GifEncoder (older version), but unfortunately the gif output is much slower or so to say, it lags. This is my code:
import GIFEncoder from "gif-encoder-2";
import fs from "fs";
import pkg from "canvas";
const { createCanvas } = pkg;
let frame = 0;
const size = 200;
const fr = 60; //starting FPS
const encoder = new GIFEncoder(size, size);
encoder
.createReadStream()
.pipe(fs.createWriteStream("my.gif"));
encoder.start();
encoder.setRepeat(0); // 0 for repeat, -1 for no-repeat
encoder.setDelay(0); // frame delay in ms
encoder.setQuality(10); // image quality. 10 is default.
var canvas = createCanvas(size, size),
cw = canvas.width,
ch = canvas.height,
cx = null,
fps = 60,
bX = 30,
bY = 30,
mX = 10,
mY = 20,
interval = null;
function gameLoop() {
cx.clearRect(0, 0, cw, cw);
cx.beginPath();
cx.fillStyle = "red";
cx.arc(bX, bY, 20, 0, Math.PI * 360);
cx.fill();
if (bX >= cw || bX <= 0) {
mX *= -1;
}
if (bY >= ch || bY <= 0) {
mY *= -1;
}
bX += mX;
bY += mY;
encoder.addFrame(cx);
console.log(frame);
if (frame > 60) {
clearInterval(interval);
encoder.finish();
}
++frame;
}
if (typeof canvas.getContext !== undefined) {
cx = canvas.getContext("2d");
interval = setInterval(gameLoop, 1000 / fps);
}
This is the output
I took the example from this fiddle, where you can see, how smooth the ball should look like.
What I tried so far without success,
Not creating a stream, when using GifEncoder
Collecting cx in an array and use GifEncoder afterwards, but it seems the ctx is a reference object and I could not find a way how to copy it
Playing around with P5 in hope, they have an internal calculation, when the deltaTime is to high between the frames
Can anyone help me here or give me an advice what to do?
It seems, I have already the solution. Setting the delay to:
encoder.setDelay(30); // frame delay in ms
Already smooths the gif:
My suggestion. Sleep a night and start with a fresh head

variable for input value isn't behaving as expected in JS

I'm working on a project. I'm creating a canvas that can be manipulated by the user. There are currently 3 input fields for the user to select Height, Width, and a 3rd value called "space".
Currently, Height and Width seem to work perfect fine, but the Space input isn't behaving as expected. For "Space" the user should be able to enter a value between 15 and 50. (space represents the distance between the circles I'm drawing).
The loop for drawing the circles is:
const lightsTop = function () {
for (let i = 5; i < cw; i += space) {
let randX = Math.random() * 10;
c.beginPath();
c.arc(i, 5 + randX, 5, 0, Math.PI * 2);
c.fillStyle = "red";
c.fill();
}
};
This code (above) works perfectly fine if I manually put in "15" (or some arbitrary number between 15-50). It generates a row of circles (which eventually will act like a string of lights), but when I use the variable "space", then it only generates 1 circle.
Here is the full JS: (the HTML is just the 3 inputs type="number",ids = #width, #height, #space)
const canvas = document.createElement("canvas");
document.body.appendChild(canvas);
const c = canvas.getContext("2d");
const widthInput = document.querySelector("#width");
const heightInput = document.querySelector("#height");
const spaceInput = document.querySelector("#space");
let cw = 500;
let ch = 300;
let space = 15;
canvas.width = cw;
canvas.height = ch;
widthInput.addEventListener("change", (event) => {
cw = widthInput.value;
if (cw < 200) cw = 200;
if (cw > 1000) cw = 1000;
canvas.width = cw;
lightsTop();
});
heightInput.addEventListener("change", (event) => {
ch = heightInput.value;
if (ch < 200) ch = 200;
if (ch > 600) ch = 600;
canvas.height = ch;
lightsTop();
});
spaceInput.addEventListener("change", (event) => {
space = spaceInput.value;
if (space < 15) space = 15;
if (space > 50) space = 50;
animate();
});
function animate() {
c.clearRect(0,0,cw,ch);
requestAnimationFrame(lightsTop);
}
const lightsTop = function () {
for (let i = 5; i < cw; i += space) {
let randX = Math.random() * 10;
c.beginPath();
c.arc(i, 5 + randX, 5, 0, Math.PI * 2);
c.fillStyle = "red";
c.fill();
}
};
You are assigning a string to the variable:
space = spaceInput.value;
It must be a number
space = parseInt(spaceInput.value);

Canvas how to animate a height from 0 to the given height.

paintForegroundBars(hours: Array<number>) {
let barColor = "#b3bec9";
let numBars = hours.length;
let barWidth = Math.floor((this.canvasWidth / numBars) - this.barsSpacing);
let maxBarHeight = this.canvasHeight - (this.timesHeight + (this.timesSpacing * 2));
let barLeft = 0 + (this.barsSpacing / 2);
this.canvasContext.fillStyle = barColor;
this.canvasContext.strokeStyle = barColor;
this.canvasContext.lineJoin = "round";
this.canvasContext.lineWidth = this.cornerRadius;
for (let i = 0; i < numBars; i++) {
let barHeight = Math.round(maxBarHeight * hours[i]);
if (barHeight > 0) {
let barTop = maxBarHeight - barHeight;
let roundedBarTop = barTop + (this.cornerRadius / 2);
let roundedBarLeft = barLeft + (this.cornerRadius / 2);
let roundedBarWidth = barWidth - this.cornerRadius;
let roundedBarHeight = barHeight - this.cornerRadius;
this.canvasContext.strokeRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
this.canvasContext.fillRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
}
barLeft = Math.floor(barLeft + barWidth) + (this.barsSpacing);
}
}
At the moment I am drawing the height of a bar chart with the below code:
this.canvasContext.strokeRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
this.canvasContext.fillRect(roundedBarLeft, roundedBarTop, roundedBarWidth, roundedBarHeight);
Instead of when this runs it just being a fixed height I want it to animate from 0 to the height that has been calculated in my JS. How do you go about doing this?
Many thanks
Below is a simple example of how this kind of animation works. The thing you are looking for is the easing value - once you have that, you are set! In this case I store the start time inside the start variable, and then you simply take the current time, remove the start time and divide it by the time you want to pass. This will give you a number between 0 and 1, and multiplying any other number by that will give you the number in the range 0 to n. If you want to add a base value to this, your general formula is basically this:
fromValue + (nowTime - sinceTime) / duration * (toValue - fromValue);
The reason the easing value is so important is that it allows tweening. For example, you can create a smooth curve by multiplying this easing value by itself:
var easing = (nowTime - sinceTime) / duration;
easing = easing * easing;
fromValue + easing * (toValue - fromValue);
Use a graphing application to learn more about these curves :)
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
var start = Date.now();
var duration = 5000;
var animationFrame = false;
canvas.width = 40;
canvas.height = 400;
function drawBar(){
var progress = (Date.now() - start) / duration;
if( progress >= 1 ){
// Final state before stopping any drawing function
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.fillRect( 0, 0, canvas.width, canvas.height );
window.cancelAnimationFrame( drawBar );
} else {
// In progress
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.fillRect( 0, 0, canvas.width, canvas.height * progress );
window.requestAnimationFrame( drawBar );
}
}
animationFrame = setInterval( drawBar, 1000 / 60 );
document.body.addEventListener('click', function( event ){
start = Date.now();
window.cancelAnimationFrame( drawBar );
window.requestAnimationFrame( drawBar );
});
<canvas></canvas>

Reusable JavaScript Component: How to make it?

I would like to create a reusable JavaScript component out of the following canvas spinner. Never done this before. How to achieve it and how to use the component?
http://codepen.io/anon/pen/tkpqc
HTML:
<canvas id="spinner"></canvas>
JS:
var canvas = document.getElementById('spinner');
var context = canvas.getContext('2d');
var start = new Date();
var lines = 8,
cW = context.canvas.width,
cH = context.canvas.height;
centerX = canvas.width / 2;
centerY = canvas.height / 2;
radius = 20;
var draw = function() {
var rotation = parseInt(((new Date() - start) / 1000) * lines) % lines;
context.save();
context.clearRect(0, 0, cW, cH);
for (var i = 0; i < lines; i++) {
context.beginPath();
//context.rotate(Math.PI * 2 / lines);
var rot = 2*Math.PI/lines;
var space = 2*Math.PI/(lines * 12);
context.arc(centerX,centerY,radius,rot * (i) + space,rot * (i+1) - space);
if (i == rotation)
context.strokeStyle="#ED3000";
else
context.strokeStyle="#CDCDCD";
context.lineWidth=10;
context.stroke();
}
context.restore();
};
window.setInterval(draw, 1000 / 30);
EDIT - SOLUTION:
Here comes a solution if anybody is interested
http://codepen.io/anon/pen/tkpqc
There are any number of ways to do this. Javascript is an object oriented language so you can easily write like:
var Spinner = function(canvas_context)
{
this.context = canvas_context;
// Whatever other properties you needed to create
this.timer = false;
}
Spinner.prototype.draw = function()
{
// Draw spinner
}
Spinner.prototype.start = function()
{
this.timer = setInterval(this.start, 1000 / 30);
}
Spinner.prototype.stop = function() {
clearInterval(this.timer);
}
Now you can use this object like so:
var canvas = document.getElementById('#canvas');
var context = canvas.getContext('2d');
var spinner = new Spinner(context);
spinner.start();
Basically, you are creating a class whose sole purpose in life is to draw a spinner on a canvas. In this example, you'll note that you're passing in the canvas's context into the object, since the details of the canvas itself is not relevant to this class's interest.

Categories