Animation optimization in pure js

Viewed 75

There is an animation of the flight of leaves in pure js. The problem is that it needs to be optimized for maximum performance, because there will be more animations of this kind on the page. Besides optimizing the original SVG image and reducing the number of leaflets, what tips can you give to improve the performance of your code?

var LeafScene = function(el) {
    this.viewport = el;
    this.world = document.createElement('div');
    this.leaves = [];

    this.options = {
      numLeaves: 6,
      wind: {
        magnitude: 0,
        maxSpeed: 12,
        duration: 300,
        start: 0,
        speed: 10
      },
    };

    this.width = this.viewport.offsetWidth;
    this.height = this.viewport.offsetHeight;

    this.timer = 0;

    this._resetLeaf = function(leaf) {

      leaf.x = this.width * 2 - Math.random()*this.width*1.75;
      leaf.y = -10;
      leaf.z = Math.random()*200;
      if (leaf.x > this.width) {
        leaf.x = this.width + 10;
        leaf.y = Math.random()*this.height/2;
      }
      if (this.timer == 0) {
        leaf.y = Math.random()*this.height;
      }

      leaf.rotation.speed = Math.random()*10;
      var randomAxis = Math.random();
      if (randomAxis > 0.5) {
        leaf.rotation.axis = 'X';
      } else if (randomAxis > 0.25) {
        leaf.rotation.axis = 'Y';
        leaf.rotation.x = Math.random()*180 + 90;
      } else {
        leaf.rotation.axis = 'Z';
        leaf.rotation.x = Math.random()*360 - 180;
        leaf.rotation.speed = Math.random()*3;
      }

      leaf.xSpeedVariation = Math.random() * 0.8 - 0.4;
      leaf.ySpeed = Math.random() + 1.5;

      return leaf;
    }

    this._updateLeaf = function(leaf) {
      var leafWindSpeed = this.options.wind.speed(this.timer - this.options.wind.start, leaf.y);

      var xSpeed = leafWindSpeed + leaf.xSpeedVariation;
      leaf.x -= xSpeed;
      leaf.y += leaf.ySpeed;
      leaf.rotation.value += leaf.rotation.speed;

      var t = 'translateX( ' + leaf.x + 'px ) translateY( ' + leaf.y + 'px ) translateZ( ' + leaf.z + 'px )  rotate' + leaf.rotation.axis + '( ' + leaf.rotation.value + 'deg )';
      if (leaf.rotation.axis !== 'X') {
        t += ' rotateX(' + leaf.rotation.x + 'deg)';
      }
      leaf.el.style.webkitTransform = t;
      leaf.el.style.MozTransform = t;
      leaf.el.style.oTransform = t;
      leaf.el.style.transform = t;

      if (leaf.x < -10 || leaf.y > this.height + 10) {
        this._resetLeaf(leaf);
      }
    }

    this._updateWind = function() {

      if (this.timer === 0 || this.timer > (this.options.wind.start + this.options.wind.duration)) {

        this.options.wind.magnitude = Math.random() * this.options.wind.maxSpeed;
        this.options.wind.duration = this.options.wind.magnitude * 50 + (Math.random() * 20 - 10);
        this.options.wind.start = this.timer;

        var screenHeight = this.height;

        this.options.wind.speed = function(t, y) {
          var a = this.magnitude/2 * (screenHeight - 2*y/3)/screenHeight;
          return a * Math.sin(2*Math.PI/this.duration * t + (3 * Math.PI/2)) + a;
        }
      }
    }
  }

  LeafScene.prototype.init = function() {

    for (var i = 0; i < this.options.numLeaves; i++) {
      var leaf = {
        el: document.createElement('div'),
        x: 0,
        y: 0,
        z: 0,
        rotation: {
          axis: 'X',
          value: 0,
          speed: 0,
          x: 0
        },
        xSpeedVariation: 0,
        ySpeed: 0,
        path: {
          type: 1,
          start: 0,

        },
        image: 1
      };
      this._resetLeaf(leaf);
      this.leaves.push(leaf);
      this.world.appendChild(leaf.el);
    }

    this.world.className = 'leaf-scene';
    this.viewport.appendChild(this.world);
    this.world.style.webkitPerspective = "400px";
    this.world.style.MozPerspective = "400px";
    this.world.style.oPerspective = "400px";
    this.world.style.perspective = "400px";
    
    var self = this;
    window.onresize = function(event) {
      self.width = self.viewport.offsetWidth;
      self.height = self.viewport.offsetHeight;
    };
  }

  LeafScene.prototype.render = function() {
    this._updateWind();
    for (var i = 0; i < this.leaves.length; i++) {
      this._updateLeaf(this.leaves[i]);
    }

    this.timer++;

    requestAnimationFrame(this.render.bind(this));
  }

  var leafContainer = document.querySelector('.falling-leaves'),
      leaves = new LeafScene(leafContainer);

  leaves.init();
  leaves.render();
body, html {
  height: 100%;
}

.falling-leaves {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100%;
  max-width: 880px;
  max-height: 880px;
  transform: translate(-50%, 0);
  border: 20px solid #fff;
  border-radius: 50px;
  background-size: cover;
  overflow: hidden;
}

.leaf-scene {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 100%;
  transform-style: preserve-3d;
}

.leaf-scene div {
  position: absolute;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  background: url(https://www.flaticon.com/svg/static/icons/svg/892/892881.svg) no-repeat;
  background-size: 100%;
  transform-style: preserve-3d;
  -webkit-backface-visibility: visible;
          backface-visibility: visible;
}
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

<div class="falling-leaves"></div>

1 Answers

Unless you're using HTML Canvas, vanilla JS solutions are pretty heavy compared to CSS/JS hybrid because of the browser call stack.

It looks like this:

JS > Style > Layout > Paint > Composite

By minimizing the amount of calculations the browser has to do in JS and grouping reads/writes to the DOM you'll see a significant performance increase. The workload can be recorded with Chrome Dev tools under 'Performance' tab. And you'll be able to see every step the browser is taking to display the content. The less steps, the better performance .. simple as that.

You're writing to the DOM every single transform which is heavy even with 3d acceleration. I usually make use of CSS variables and transitions for dynamic animation that I update in steps.

What you did is great otherwise. Try rendering on a HTML Canvas and your performance problems will vanish. Here's a start:

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API

But if you want or cant do canvas adapt to utilize CSS to not drop further than the Paint browser call for majority of frames.

Related