I wanted a Scroll-Based Animation with image sequences like this:
http://www.clearmotion.com/technology
This animation seems scroll smoothly
I've tried with this fiddle
var images = new Array();
var currentLocation = 0;
var totalImages = 200;
for (var i = 1; i < totalImages; i++) {
var img = new Image;
var slug = '000' + i;
img.src = 'https://s3.amazonaws.com/clearmotion/hero/high-min/frame'+ slug.slice(-3) +'-low.jpg'
images.push(img);
}
var c = document.getElementById("background");
var ctx = c.getContext("2d");
var mouseWheel = function () {
window.addEventListener('mousewheel', function (e) {
e.preventDefault(); // No scroll
// The following equation will return either a 1 for scroll down
// or -1 for a scroll up
var delta = Math.max(-1, Math.min(1, e.wheelDelta));
// This code mostly keeps us from going too far in either direction
if (delta == -1) currentLocation += 1;
if (delta == 1) currentLocation -= 1;
if (currentLocation < 0) currentLocation = 0;
if (currentLocation >= (totalImages - 1)) currentLocation = (totalImages - 1);
console.log("Current location " + currentLocation);
// See below for the details of this function
setImage(currentLocation);
});
}
var setImage = function (newLocation) {
// drawImage takes 5 arguments: image, x, y, width, height
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(images[newLocation], 0, 0, 1000, 1000);
}
images[0].onload = function () {
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(images[currentLocation], 0, 0, 1000, 1000);
mouseWheel();
};
<canvas id="background" width="1000" height="1000"></canvas>
`
however, the effect looks terrible.
when my mouse stop scrolling, the animation stop immediately
how can I make it more smooth?
This happens because the wheel event might fire at a very high rate, higher than your screen refresh rate.
This means that for every frame painted to the screen, you actually painted several frames on the canvas. The browser then has some issues to handle all these requests, and end up creating lag.
To avoid it, you can throttle your event: execute your callback conditionally, only if it's been more than some predefined time it didn't already fired.
When dealing with visual animations, a good base for such throttling time is the one of the screen refresh. And we've got a timing method that does exactly this: requestAnimationFrame
.
var images = new Array();
var currentLocation = 0;
var totalImages = 200;
for (var i = 1; i < totalImages; i++) {
var img = new Image;
var slug = '000' + i;
img.src = 'https://s3.amazonaws.com/clearmotion/hero/high-min/frame' + slug.slice(-3) + '-low.jpg'
images.push(img);
}
var c = document.getElementById("background");
var ctx = c.getContext("2d");
var mouseWheel = function() {
var evt = null; // avoids a new 'draw' function generation
window.addEventListener('wheel', function(e) {
e.preventDefault(); // No scroll
if (!evt) { // if set, we already are waiting
evt = e; // store our event
requestAnimationFrame(draw); // wait next screen refresh to fire
}
});
// the throttled funtion
function draw() {
var e = evt;
var delta = Math.max(-1, Math.min(1, e.deltaY));
if (delta == -1) currentLocation += 1;
if (delta == 1) currentLocation -= 1;
if (currentLocation < 0) currentLocation = 0;
if (currentLocation >= (totalImages - 1)) currentLocation = (totalImages - 1);
setImage(currentLocation);
evt = null; // so the throttler knows we can kick again
}
}
var setImage = function(newLocation) {
if (!images[newLocation]) return;
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(images[newLocation], 0, 0, 1000, 1000);
}
images[0].onload = function() {
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(images[currentLocation], 0, 0, 1000, 1000);
mouseWheel();
};
<canvas id="background" width="1000" height="1000"></canvas>
Now, since we are blocking the whole event handling, your animation might also look like being slower (even if smoother). To circumvent this, you can still update your variables as fast as you can, and only throttle the actual drawing.
var images = new Array();
var currentLocation = 0;
var totalImages = 200;
for (var i = 1; i < totalImages; i++) {
var img = new Image;
var slug = '000' + i;
img.src = 'https://s3.amazonaws.com/clearmotion/hero/high-min/frame' + slug.slice(-3) + '-low.jpg'
images.push(img);
}
var c = document.getElementById("background");
var ctx = c.getContext("2d");
var mouseWheel = function() {
var newLocation = null;
window.addEventListener('wheel', function(e) {
e.preventDefault(); // No scroll
// update our variable at high frequency
var delta = Math.max(-1, Math.min(1, e.deltaY));
if (delta == -1) currentLocation += 1;
if (delta == 1) currentLocation -= 1;
if (currentLocation < 0) currentLocation = 0;
if (currentLocation >= (totalImages - 1)) currentLocation = (totalImages - 1);
if (newLocation === null) { // if set, we already are waiting to draw
requestAnimationFrame(setImage);
}
newLocation = currentLocation;
});
function setImage() {
if (images[newLocation]) {
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(images[newLocation], 0, 0, 1000, 1000);
}
newLocation = null; // so the throttler knows we can draw again
}
}
images[0].onload = function() {
ctx.fillRect(0, 0, c.width, c.height);
ctx.drawImage(images[currentLocation], 0, 0, 1000, 1000);
mouseWheel();
};
<canvas id="background" width="1000" height="1000"></canvas>
Note that I did update your event to use the only specs compliant one: WheelEvent.
Also note that you might be better loading a video instead of that many still images. Or even, convert your original 3D animation to webGL.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With