# Creating smooth animations using rAF

It is my first time trying to create smooth animations using rAF and I would like some insight from more experienced developers. Basically I have 16 elements with x,y coordinates and on mousemove over their parent container, all elements move a set amount in the opposite direction. When I do tests on lower-end machines there is some stutter. Is there any technique I am not aware of that can boost my animation performance?

(function ($) {$(document).ready(() => {
const animContWidth = animCont.offsetWidth;
const animContHeight = animCont.offsetHeight;
const leftOffset = animCont.getBoundingClientRect().left;
const topOffset = document.querySelector('#primary').offsetTop;
const animImgs = animCont.querySelectorAll('.elem');
let mouseX = 0;
let mouseY = 0;
let mouseOver = false;
let positions = [
{
id: 82,
start: {
x: 0.596,
y: 0.654,
},
range: 0.4,
}
...
]
positions.forEach(el => {
el.target = {};
el.current = {};
})
function updateElements(){
mouseTargetX = (mouseX - leftOffset) / animContWidth;
mouseTargetY = (mouseY + topOffset) / animContHeight;
positions.forEach((el,i) => {
if (mouseOver) {
el.target.x = ((mouseTargetX - el.start.x) * -el.range + el.start.x ) * animContWidth;
el.target.y = ((mouseTargetY - el.start.y) * -el.range + el.start.y ) * animContHeight;
} else {
el.target.x = el.start.x * animContWidth;
el.target.y = el.start.y * animContHeight;
}

if(!el.current.x) {
el.current.x = el.target.x;
el.current.y = el.target.y;
} else {
el.current.x = el.current.x + (el.target.x - el.current.x)*0.1;
el.current.y = el.current.y + (el.target.y - el.current.y)*0.1;
}

animImgs[i].style.transform = 'translate3d('+el.current.x+'px,'+el.current.y+'px,0)';
})
requestAnimationFrame(updateElements);
}
updateElements();
function animContMouseEnter(e){
mouseOver = true;
}
function animContMouseLeave(){
mouseOver = false;
}
function animContMouseOver(e){
mouseX = e.clientX;
mouseY = e.clientY;
}
});
}(jQuery));


There is not enough information in your question to get a clear picture of where there may be a problem.

You code does not have any glaring performance issues. Some minor changes would be...

• Use for loops rather than forEach. (Minor performance increase but when animating its always good to use the fastest style)

• Don't use 3D transforms if you are only doing 2D animation. Better yet, if only 2d dont use the transform ate all. Set thestyle.positiontoabsoluteand use thetop,left CSS style attributes to set position.

• You only use jQuery to get the onload event. Its just bloat so don't load it if you don't need it.

## Animating elements.

Some points related to animating elements.

Its not just the elements that you are animating that add to the total load of an animation. Other elements may require rendering (compositing) and they may also be triggering reflow/s. Composites and reflows are expensive operations

The order of the animated elements in relationship to other content can have a big impact on performance due to inefficient compositing. For example you may be animating items A and B, and on the page you have c, A, d, B, e (where c,d,e are other elements) . In the worst case the compositing of the content will be as follows... render c, composite c with background, render A, composite A with previous composite, render b, composite b with previous composite, and so on.

So you just change and move 2 items but that may trigger 5 renders and 5 composites, and depending on the layout, they may be the 5 full screen composites and renders each frame.

Are you using filters? Depending on the filters (and the GPU's abilities) they can add to the workload. Use filters sparingly, don't make more than one change to filters during a frame.

Can all the image and pixel related content fit into GPU RAM. If not reduce the number of pixels. Low end devices can have very low amounts of GPU RAM and if you exceed the capacity of the GPU the browser will swap it in and out of CPU RAM as needed. This is SLOW... very SLOW...

## Take charge of the rendering.

It can be very difficult to locate animation bottle necks when animating elements.

One way to remove the rendering/compositing ambiguity is to use the canvas. You then get to efficiently control the rendering compositing process. The canvas 2D and WebGL API's use the same code (there abouts) as the browsers rendering and compositing systems.

With some minor changes your animation items can be rendered on a canvas, needing only one render pass and one composite (if you also render the background you can eliminate composite)

The snippet is an example of canvas rendering performance. There are 500 bugs each rendered with a full transform scale, position, and rotated. This you would never be able to do using HTML elements.

requestAnimationFrame(update); // Start animation when ready

const image = new Image;
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
const canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);

const mouse  = {x : 0, y : 0}
mouse.x = e.pageX;
mouse.y = e.pageY;
});

var w,h;
function resize(){
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
}
resize();
const rand  = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const DO = (count,callback) => { while (count--) { callback(count) } }
const sprites = [];
DO(500,()=>{
sprites.push({
x : rand(w),
y : rand(h),
xr : 0,
yr : 0, // actual position of sprite
r : rand(Math.PI * 2),
scale : rand(0.1,0.25),
dx : rand(-2,2),
dy : rand(-2,2),
dr : rand(-0.1,0.1),
});
});

// Draws a sprite.
function drawImage(image, spr){
ctx.setTransform(spr.scale, 0, 0, spr.scale, spr.xr, spr.yr); // sets scales and origin
ctx.rotate(spr.r);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
}

// Main animation loop
function update(time){
var ihM,iwM,dx,dy,spr,i,iw,ih,dist;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
const runHalf = time / 3000;
if(image.complete){
iw = image.width;
ih = image.height;
const size = (iw * iw + ih * ih) / 2;
for(i = 0; i < sprites.length; i ++){
spr = sprites[i];
dx = mouse.x - spr.xr;
dy = mouse.y - spr.yr;
dist = dx * dx + dy * dy;
if(dist/ spr.scale < size){
const dir = -Math.atan2(dy,dx) -0.2 + rand(0.4);
const speed = rand(18);
spr.dx += Math.cos(dir) * speed;
spr.dy += Math.sin(dir) * speed;

}else if(((i + runHalf) | 0) % 2){ // half run away
spr.dx += dx * 0.001 + Math.cos(spr.r) * 0.1;
spr.dy += dy * 0.001 + Math.sin(spr.r) * 0.1;
}else{
spr.dx += -dx * 0.001 + Math.cos(spr.r) * 0.1;
spr.dy += -dy * 0.001 + Math.sin(spr.r) * 0.1;
}

spr.dx *= 0.99;
spr.dy *= 0.99;
// limit speed
dist = spr.dx * spr.dx + spr.dy * spr.dy;
if(dist > 54){
spr.dx *= 0.9;
spr.dy *= 0.9;
}

spr.x += spr.dx;
spr.y += spr.dy;
spr.r += spr.dr;
iwM = iw * spr.scale * 2 + w;
ihM = ih * spr.scale * 2 + h;
spr.xr = ((spr.x % iwM) + iwM) % iwM - iw * spr.scale;
spr.yr = ((spr.y % ihM) + ihM) % ihM - ih * spr.scale;
drawImage(image,spr);
}
}
requestAnimationFrame(update);
}`

• Thank you, I will look into maybe doing all this with canvas – Hristo Hristov Aug 11 at 7:28