danillosl
danillosl

Reputation: 519

My css animation is consuming a lot of resources

I'm trying to make a starring night with twinkling stars in css3 + Javascript, however, my animation is consuming a lot of CPU, the main animation:

  @for $i from 0 through 400 {
  .star:nth-child(#{$i}) {
    $star-size: (random() * (1-4) +4) + px;
    top: (random(100)) + vh;
    left: (random(100)) + vw;
    width: $star-size;
    height: $star-size;
    animation: blinker 1.2s alternate infinite ease-in-out;
    animation-delay: (random(30) / 10) + s;
    transform: scale(0.2);
  }
}

@keyframes blinker {
  100% {
    transform: scale(1);
  }
}

the full code: https://jsfiddle.net/sam7krx0/ is there any way to make this code perform better?

Edit:

tried with translateZ(0) and with will-change: transform but the animation still being rendered by the CPU.

https://jsfiddle.net/8hn97kcx/2/

Edit 2: It seems that firefox might be the problem, while testing on chrome the animation uses way less CPU.

Edit 3:

profile of the fiddle above running on firefox developer edition 69.0b4:

firefox profile

CPU usage: System monitor

Upvotes: 2

Views: 842

Answers (4)

zer00ne
zer00ne

Reputation: 44086

The OP code was horrendously inefficient in that it uses 400+ uniquely generated selectors. So the bulk of the processing time involves maintaining the CSS animation loop and looking up 400+ classes on each alternation of said CSS animation. This is a rare case wherein class selectors are a burden and not useful. Since each s.star needs these unique styles, it would take less computing power to generate the CSS property values on a template literal and then assign it to the tag as an inline-style. (See Demo)

Besides doing away with ridiculously huge .class lists on a bloated stylesheet, the demo makes full use of a documentFragment. DOM operations are expensive on resources (imagine 400+ tags being appended to one location). Doing everything on the fragment, then finally to the DOM by 👍appending documentFragment just once and 400 .star are in the DOM👍. The OP code on the other hand 👎will append 400 s.star one at a time... that's 400+ DOM operations.👎

Also on the OP code it is deceiving as to the size of the actual CSS. SCSS, a post-processor is used, so what looks like 8 lines of weird looking CSS is actually 👎3200 lines of CSS👎 after it has been compiled and cached by the browser. The CSS in the demo is what it appears to be ...👍9 lines👍 for .star selector.

/**| documentFragment
- The only global variable points to a documentFragment not attached to the DOM. 
- Use fragment as you would a document object when moving, creating, destroying, 
  appending, detaching, etc... HTML element tags from and to the DOM. Doing so will 
  greatly improve processing times when adding 400+ uniquely styled tags. 
- When all .star tags have been created, modified, and appended to the fragment -- 
  only the fragment itself needs to be appended to the DOM instead of 400 tags.
*/
var fragment = document.createDocumentFragment();

/**| randomRange(min, max, integer = false)
   @Params: min [number].....: The minimum
            max [number].....: The maximum
            integer [boolean]: default is false which results will be floats. 
                               If true then results will be integers.
Utility function that will return a random number from a given range of consecutive 
numbers.
*/
const randomRange = (min, max, integer = false) => {
  let numbers = integer ? {
    min: Math.ceil(min),
    max: Math.floor(max)
  } : {
    min: min,
    max: max
  };
  return Math.random() * (numbers.max - numbers.min + 1) + numbers.min;
};

/**| starGenerator(limit)
   @Params: limit [number]: The number of s.star to generate.
A generator function that creates s.star tags. Assigning individual tag properties
and setting randomly determined values would involve a ton of unique selectors. 
To avoid a ton of lookups in a CSS stylesheet a mile long, it's easier to create and 
maintain one template literal of the CSS properties interpolated with random values. 
Each s.star would be assigned an inline-style of five CSS properties/values by one 
statement via `.cssText` property. 
*/
function* starGenerator(limit) {
  let iteration = 0;
  while (iteration < limit) {
    iteration++;
    const star = document.createElement("s");
    star.classList.add("star");
    let properties = `
     width: ${randomRange(1, 4)}px; 
     height: ${randomRange(1, 4)}px;
     top: ${randomRange(0, 100, true)}vh; 
     left: ${randomRange(0, 100, true)}vw;
     animation-delay: ${randomRange(1, 30, true) / 10}s`;
    star.style.cssText = properties;
    yield star;
  }
  return fragment;
}

/**| nightfall(selector, limit = 400)
   @Params: selector [string]: Target parent tag
            limit [number].. : The maximum number of s.star to generate.
Interface function that facilitates DOM procedures with minimal presence in DOM.
*/
const nightfall = (selector, limit = 400) => {
  const base = document.querySelector(selector);
  base.classList.add('sky');
  for (let star of starGenerator(limit)) {
    fragment.appendChild(star);
  }
  return base.appendChild(fragment);
};

// Call nightfall() passing the selector "main"
nightfall("main");
.sky {
  position: relative;
  background: #000;
  height: 100vh;
  overflow: hidden;
}

.star {
  display: block;
  position: absolute;
  animation: twinkle 1.2s alternate infinite ease-in-out;
  transform: scale(0.2);
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 6px 1px #fff;
  z-index: 2;
  text-decoration: none;
}

@keyframes twinkle {
  100% {
    transform: scale(1);
  }
}
<main></main>

Upvotes: 2

jhummel
jhummel

Reputation: 1764

Have you tried using the will-change property - this helps the browser know about the change and offload it to the compositor if possible.

Upvotes: 2

Andrei Todorut
Andrei Todorut

Reputation: 4536

That's because the rendering is done by CPU which can be a loose in performance. There is an option in CSS to run such an animation on GPU.

Your snippet adjusted

  @for $i from 0 through 400 {
  .star:nth-child(#{$i}) {
    $star-size: (random() * (1-4) +4) + px;
    transform: translateY((random(100)) + vh) translateX((random(100)) + vw) translateZ(0);
    width: $star-size;
    height: $star-size;
    animation: blinker 1.2s alternate infinite ease-in-out;
    animation-delay: (random(30) / 10) + s;
    transform: scale(0.2);
  }
}

@keyframes blinker {
  100% {
    transform: scale(1);
  }
}

It's very important to add translateZ because only 3D renderings are done by GPU.

Doing animations on GPU is also called accelerated animations, please check this helpful article for more information about: https://www.sitepoint.com/introduction-to-hardware-acceleration-css-animations/

Upvotes: 1

Roman Traversine
Roman Traversine

Reputation: 928

it's not only problem with you code. it's also from your CPU ability, trying to upgrade your CPU and RAM to perform better.

sometimes you can't build mid - high animation in low spec computer.

Upvotes: 0

Related Questions