Skip to content

Latest commit

 

History

History
363 lines (285 loc) · 9.69 KB

performance.md

File metadata and controls

363 lines (285 loc) · 9.69 KB

Browser Performance

How the browser works

First, read this and everything it references below.

Advices

  • Force layout/reflow only when it's needed and not every frame. The browser has to recalculate all the page layout.
// BAD
function update() {
  // trigger layout/reflow every frame
  const rect = node.getBoundingClientRect();
  //process
  requestAnimationFrame(update);
}

requestAnimationFrame(update);
// GOOD

let rect;

function resize() {
  // trigger layout/reflow on window resize and cache the value
  rect = node.getBoundingClientRect();
}

resize();
window.addEventListener("resize", resize, false);

function update() {
  //process
  requestAnimationFrame(update);
}

requestAnimationFrame(update);

  • Reduce the amount of DOM elements, avoid using unnecessary elements. The more elements you have the more time the browser will take to recalculate. Also, try to keep the HTML as semantic as possible
// BAD
<div class="accordion">
  <div class="accordion__head">
    <div>
      <span>title</span>
      <div>
        <div>
          <div class="accordion__content">
            <div>
              <span>content</span>
              <div>
                <div></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
// GOOD
<div class="accordion">
  <div class="accordion__head">
    <p>title</p>
    <div>
      <div class="accordion__content">
        ...content
        <div></div>
      </div>
    </div>
  </div>
</div>

  • Do not update React state too often, only render it when it's needed. Every time a state changes it will render the component and all its children. You also can use useMemo to avoid rendering. Use console.log('update') above the return to see how many times the component is rendered.
// BAD
const Cursor = () => {
  const [mouse,setMouse] = useState({x=0,y=0})

  function onMouseMove({pageX,pageY}) {
    // render component every mousemove events
    setMouse({x:pageX,y:pageY})
  }

  useLayoutEffect(()=>{
    window.addEventListener('mousemove',onMouseMove,false)
  },[])

  console.log('update')

  return <div style={"transform":`translate3d(${x}px,${y}px,0)`}></div>
}
// GOOD
const Cursor = () => {
  const ref = useRef();

  function onMouseMove({ pageX, pageY }) {
    // render component once
    ref.current.style.setProperty(
      "transform",
      `translate3d(${pageX}px,${pageY}px,0)`
    );
  }

  useLayoutEffect(() => {
    window.addEventListener("mousemove", onMouseMove, false);
  }, []);

  console.log("update");

  return <div ref={ref}></div>;
};

  • Prefer using CSS transition over Spring when it's possible. Animation libs such as GSAP or react-spring apply styles on every frame on the targeted elements. It triggers layout/reflow every frame versus toggling a class/style only triggers layout/reflow once.
// BAD
const Fade = ({ children, active }) => {
  const [style] = useSpring(
    () => ({
      opacity: active ? 1 : 0,
    }),
    [active]
  );

  return <a.div style={style}>{children}</a.div>;
};
// GOOD
const Fade = ({children, active}) => {
  return (
    <div style={"opacity": active ? 1 : 0, "transition": "opacity 1s ease-out"}>
      {children}
    </div>
  )
}

  • Only use a single, unique, requestAnimationFrame. See performance benchmark here.
// BAD
function update() {
  // process
  requestAnimationFrame(update);
}

requestAnimationFrame(update);
import { raf } from "@react-spring/rafz";

// GOOD
function update() {
  // process
  return true;
}

raf.onFrame(update);

  • Leverage hardware acceleration on costly CSS operations. Add will-change: transform or transform: translateZ(0) to force hardware acceleration.
// BAD
function onMouseMove({ pageX, pageY }) {
  // no hardware acceleration
  node.style.setProperty("transform", `translate(${pageX}px,${pageY}px)`);
}

window.addEventListener("mousemove", onMouseMove, false);
// GOOD
function onMouseMove({ pageX, pageY }) {
  // translate3d leverage hardware acceleration
  node.style.setProperty("transform", `translate3d(${pageX}px,${pageY}px,0)`);
}

window.addEventListener("mousemove", onMouseMove, false);

  • Prefer animation optimized CSS properties as opacity or transform. See here.
// BAD
function onMouseMove({ pageX, pageY }) {
  // animating top is costly
  node.style.setProperty("left", `${pageX}px`);
  node.style.setProperty("top", `${pageY}px`);
}

window.addEventListener("mousemove", onMouseMove, false);
// GOOD
function onMouseMove({ pageX, pageY }) {
  // animating transform is cheap
  node.style.setProperty("transform", `translate3d(${pageX}px,${pageY}px,0)`);
}

window.addEventListener("mousemove", onMouseMove, false);

  • Use your browser devtools performances tab to figure out costly operations. Keep in mind to run 60fps, a browser frame execution duration shouldn't exceed 16ms.

  • Reduce CSS size. Using media queries, prefer adding CSS properties than remove. The less CSS properties has an element the faster it will be to recalculate on layout/reflow. It's better for bundle size too.
// BAD
nav {
  transform: translateY(100%);
  opacity: 0;

  &--open {
    transform: translateY(0);
    oapcity: 1;
  }
}
// GOOD
nav {
  // removed 2 lines
  &--close {
    transform: translateY(100%);
    opacity: 0;
  }
}

  • Consider animate only elements visible on screen. Use IntersectionObserver. With locomotive-scroll it's always better to use data-scroll-section. It will transform only visible parts of your page instead of all page.
// BAD
<div data-scroll-container>
  <section></section>
  <section></section>
  <footer></footer>
</div>
// GOOD
<div data-scroll-container>
  <section data-scroll-section></section>
  <section data-scroll-section></section>
  <footer data-scroll-section></footer>
</div>

  • Use next/image. Don't forget to use sizes prop: (min-width: 800px) [desktop size]vw, [mobile]vw. Apply priority prop on first visible images on landing as hero. Images below can just take loading="eager"
// BAD <image src="image.jpg"></image>
// GOOD <image src="image.jpg" sizes="(min-width: 800px) 30vw, 100vw"></image>

Execute only necessary code.

  • Only add a component to its specific use case. In other words, prefer to remove the component than to disable it. So the component's effect will be executed only when it's necessary.
// BAD
<Slider enabled="{mobile}">
  <div></div>
  <div></div>
  <div></div>
  <Slider></Slider
></Slider>
// GOOD { mobile ?
<Slider>
  <div></div>
  <div></div>
  <div></div>
  <Slider>
    :
    <div>
      <div></div>
      <div></div>
      <div></div>
      <div>}</div>
    </div></Slider
  ></Slider
>

Single responsibility principle

Every class should have only one responsibility SOLID. Its name should describe the exact behaviour without any doubts.

// BAD
.hidden-overflow {
  @include mobile {
    overflow: hidden;
  }
}
// GOOD
.hidden-overflow {
  overflow: hidden;
}

or .hidden-overflow-mobile {
  @include mobile {
    overflow: hidden;
  }
}