Back

Devlog: playing around with animation: pixi js

I had a bit of a rest this weekend, but I decided to stop procrastinating and finally try animating the Freebites website with Pixi.js.

The section where the phone is at is supposed to be animated, and for that, we’re using a combination of anime.js and Pixi.js.

What's that?

anime.js is an animation library that allows you to…animate things. So is Pixi. So why use two animation libraries? Pixi.js a WebGL renderer, used for animating actual animations, or even games, so I can animate sprites, filters, and entire scenes. This is ideal for complex animations that will be on the phone. On the other hand, anime.js will be used for animating interactable UI components directly in the DOM, like <p> or <div> tags. Most importantly, it syncs with user interactions, like scrolling:

  // animate on scroll
        animate(topHeroRef.current, {
          opacity: { from: 1, to: 0, duration: 350, ease: "in(3)" },
          autoplay: onScroll({
            target: topHeroRef.current,
            container: ".page",
            enter: "top+=10% top",
            leave: "top+=10% bottom-=20%",
            repeat: true,
            sync: 1,
            // debug: true,
          }),
        });

        animate(popupWrapperRef.current, {
          opacity: { from: 1, to: 0, duration: 350, ease: "in(3)" },
          translateY: {
            from: 0,
            to: 150,
            ease: "linear",
            duration: 1000,
          },
          autoplay: onScroll({
            target: topHeroRef.current,
            container: ".page",
            enter: "top+=10% top",
            leave: "top+=10% bottom+=20%",
            repeat: true,
            sync: 0.1,
            // debug: true,
          }),
        });
      }

I can trigger methods from these scroll observers to trigger animations in the Pixi.js canvas (aka the phone), like .call(handleAnimation);

So, what did I do?

Nothing much on the Pixi side, as I’m still playing around with it. I set up the groundwork and connected the different images to different states, but I’m still trying to figure out how to make actual animated transitions.

Instead, I spent my time creating an idle animation:

  useEffect(() => {
    if (!containerRef.current) return;

    const animate = (delta: number) => {
      if (!isIdling || !containerRef.current || isTransitioning) return;

      idleTimeRef.current += delta * 0.01;
      const time = idleTimeRef.current;
      const container = containerRef.current;

      // Apply idle animations to the entire container
      const scaleOffset = Math.sin(time) * 0.02;
      container.scale.x = 1 + scaleOffset;
      container.scale.y = 1 + scaleOffset;

      const floatOffset = Math.sin(time * 0.7) * 3;
      container.y = 400 + floatOffset;

      container.rotation = Math.sin(time * 0.5) * 0.03;
    };

    animateRef.current = animate;
    app.ticker.add(() => animate(0.5));

    return () => {
      if (animateRef.current) {
        app.ticker.remove(() => animateRef.current);
        animateRef.current = null;
      }
    };
  }, [app.ticker, isIdling, isTransitioning]);

  // Reset idle state
  useEffect(() => {
    if (!isIdling && containerRef.current) {
      const container = containerRef.current;
      container.scale.set(1);
      container.y = 400;
      container.rotation = 0;
    }
  }, [isIdling]);

That’s all for now. Signing off!