Remotion + Anime.js: Boost Remotion Animation Efficiency

Remotion + Anime.js: Boost Remotion Animation Efficiency

RemotionRemotionAnimationanime.jsreact

December 18, 20240

Introduction

While Remotion excels as a video production framework, its built-in animation utilities can present challenges when crafting intricate animations. This complexity often leads to verbose code that can be difficult to manage and maintain.

How can we streamline our animation code to enhance readability and reduce complexity?

After researching GSAP, Anime.js, and Framer Motion, I chose to combine Anime.js v4 with Remotion for my animation needs

Built-in Animation Capabilities

Let's give a brief overview of Remotion's animation capabilities:

Remotion Animation is Frame Driven, it uses hooks and functions provided by Remotion such as useCurrentFrame, spring, and interpolateColors to achieve animations.

A simple FadeIn demo using Remotion's animation capabilities:

export const FadeIn = () => {
  const frame = useCurrentFrame();
 
  const opacity = Math.min(1, frame / 60);
 
  return (
    <AbsoluteFill
      style={{
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "white",
        fontSize: 80,
      }}
    >
      <div style={{ opacity: opacity }}>Hello World!</div>
    </AbsoluteFill>
  );
};
# Explain
In this cases, we must calculate each item style for every frame, you need to calculate properties for each element frame by frame, pass them to the `spring` `Math.min` `interpolateColors` `interpolate` function,

It's fine for simple animation, but when it comes to complex animation, it's not easy to calculate the style for each element frame by frame,

Consider animating three elements with distinct styles, modifying properties like opacity, border radius, color, and font size concurrently or sequentially.

Like this:

Scoped
Nest Scoped
Global
0:00 / 0:30

This scenario presents significant challenges:

  • I wrote 176 lines in Remotion-only Code, and I've found that it's hard to keep track of the animation logic for multiple elements.
  • Calculating styles for each element on a frame-by-frame basis becomes complex
  • Achieving smoother animations requires frequent adjustments to animation logic

While Remotion does offer a built-in animation API, it has limitations:

  • It operates at a low level, requiring prop passing throughout the component tree
  • If you use context, you may find that contextualized components are harder to reuse
  • Complex animations can lead to code that's difficult to maintain

To streamline animations, we can use React for layout and specialized libraries like Framer Motion, GSAP, or Anime.js. This approach aligns with web development practices, simplifying code and improving maintainability.

Anime.js offers a powerful timeline API that maps frames to animation sequences. By focusing on defining animations for css selectors, we offload timing and interpolation to Anime.js, reducing code complexity and enhancing readability.

Note: While GSAP is equally powerful and capable of similar results, we opted for Anime.js to eliminate additional licensing costs for our users.

Combine Anime.js with Remotion

Let's wrap our animation logic in a custom hook, moving away from frame-based logic:

// I use Anime.js v4 beta, but v3 should work similarly
import { useEffect, useId, useRef } from "react";
import { useCurrentFrame, useVideoConfig } from "remotion";
import { Timeline } from "@juliangarnierorg/anime-beta";

export const useAnimeTimeline = <T extends HTMLElement>(
  animeTimelineFactory: () => Timeline,
) => {
  // const animationScopeRef = useRef<T>(null);
  const timelineRef = useRef<Timeline | null>(null);
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const id = useId();

  useEffect(() => {
    timelineRef.current = animeTimelineFactory();
    timelineRef.current.pause();
    return () => {};
  }, []);

  useEffect(() => {
    if (timelineRef.current) {
      timelineRef.current.seek((frame / fps) * 1000);
    }
  }, [frame, fps]);

  return id.replaceAll(":", "_");
};
// Now, let's create an incredible animation sequence
import React from "react";
import { useAnimeTimeline } from "@/revid/vd/useAnimeTimeline";
import { createTimeline } from "@juliangarnierorg/anime-beta";

export const AnimeJSDemo: React.FC = () => {
  const numberOfEls = 360;

  const scopeRef = useAnimeTimeline(() => {
    const delay = 10;
    const tl = createTimeline({
      defaults: {
        loop: true,
      },
    });
    tl.add(".el", {
      opacity: [{ to: [0, 0.5], duration: 250, ease: "linear" }],
      rotate: [{ to: (el, i) => 90 + i * 2, duration: 2000 }],
      scale: [
        { to: [1, 0], duration: 2000, delay: (el, i) => 4000 + i * delay },
      ],
      delay: (el, i) => i * delay,
      ease: "outElastic",
      loop: true,
    });

    return tl;
  });

  return (
    <div
      className="absolute w-full flex justify-center items-center h-full"
      id={scopeRef}
    >
      <div className="flex flex-col flex-wrap w-full h-full">
        {Array.from({ length: numberOfEls }).map((_, i) => (
          <div
            key={i}
            className="el w-[1px] h-[200%] opacity-0 origin-center"
            style={{
              backgroundColor: `hsl(${Math.round(i / 3)}, 50%, 50%)`,
            }}
          />
        ))}
      </div>
    </div>
  );
};

export default AnimeJSDemo;

You don't need to worry about frames or passing props everywhere. And Boom, you get the result:

0:00 / 0:30

This animation is inspired by Julian Garnier's CodePen demo

Now let's examine our first demonstration: Three Squares Animation

This integration with Anime.js significantly reduces code complexity and improves maintainability:

  • Before: 176 lines of Remotion-only code
  • After: 67 lines of concise Remotion + Anime.js code

Beyond code reduction, this approach significantly enhances animation fine-tuning. By using CSS selectors and Anime.js within components, it mirrors web animation practices. This approach enables you to debug animations independently of Remotion in a standard web environment. Once perfected, these animations can be seamlessly integrated into Remotion, significantly streamlining the development process and enhancing overall efficiency.

Before 176 loc

  • Relies on Remotion's Built-in Animation API: Uses hooks and functions provided by Remotion such as useCurrentFrame, spring, and interpolateColors to achieve animations.
  • Frame-Driven Animation: The animation logic is tied to the current frame number (useCurrentFrame). The spring function calculates the value of each animated property for every frame.
  • Fine-Grained Control: Allows precise control over how each property changes frame-by-frame, including easing effects.
  • Verbose Code: Can lead to a considerable amount of code, particularly for complex animations, as each property's frame-by-frame transformation needs explicit definition.
  • Independent Element Control: Each animated element has its own separate animation logic, even if they share similar animation styles.
  • Challenging to Maintain: Modifying animation effects requires altering the animation logic of each individual element.
  • Less Readable: The code can be harder to grasp due to the need to interpret the actions of each spring and interpolateColors function.

After 67 loc

  • Uses Anime.js: Leverages the anime-beta library (a variant of Anime.js) to handle animation.
  • Timeline-Driven Animation: Employs createTimeline to create an animation timeline, upon which animations are added.
  • Declarative and Concise: Uses a CSS-like syntax to specify the start and end states of animations, as well as easing effects.
  • Simplified Code: Code is more compact and easier to read. Animation for multiple elements can be controlled through a central timeline.
  • Easier Maintenance: Facilitates centralized animation management, making modifications and maintenance more straightforward.
  • Requires a DOM Structure: Anime.js needs CSS selectors to identify which elements should be animated.
  • Depends on useAnimeTimeline: Uses a custom useAnimeTimeline hook to integrate Anime.js with Remotion. This hook creates and manages a DOM node within remotion and exposes it.

Conclusion

Here's a simple comparison table highlighting the key differences:

FeatureBefore Anime.jsAfter Anime.js
Animation EngineRemotion's built-in APIAnime.js (Timeline-based)
Animation StyleFrame-drivenTimeline-driven
Code VolumeHighLow
ReadabilitySlightly less readableBetter readability
MaintainabilityLess maintainableMore maintainable
FlexibilityFine-grained control per frame, but requires more codeEasier to describe animation effects, with less granular control possible
DependenciesRemotion APIAnime.js, Custom Hook

Ultimately, the best approach depends on your project's specific requirements and your team's expertise.

For me, Anime.js has proven to be the optimal choice, offering the best balance of power, flexibility, and ease of use.

Code in the article uses Anime.js v4 beta. v3 should work similarly. If you want to use v4 beta, you can either wait for its official release or consider sponsoring juliangarnierorg for early access and support.