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 animCont = document.querySelector('.shop-header-container');
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;
}
animCont.addEventListener("mousemove",animContMouseOver,false);
animCont.addEventListener("mouseenter",animContMouseEnter,false);
animCont.addEventListener("mouseleave",animContMouseLeave,false);
});
}(jQuery));

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

Your code

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}
document.addEventListener("mousemove", (e) => {
	mouse.x = e.pageX;
	mouse.y = e.pageY;
});

var w,h;
function resize(){ 
    w = canvas.width = innerWidth;
    h = canvas.height = innerHeight;
}
resize();
addEventListener("resize",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

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.