Skip to content

Instantly share code, notes, and snippets.

@bjszd
Last active December 8, 2023 17:42
Show Gist options
  • Save bjszd/32eb045fe4db50605c3e4943b986465c to your computer and use it in GitHub Desktop.
Save bjszd/32eb045fe4db50605c3e4943b986465c to your computer and use it in GitHub Desktop.
Working with Lottie files

Working with Lottie files

Lottie is the file type of Bodymovin, a plugin for AfterEffects that can export an animation into a descriptive JSON format.

Why is it called Lottie files?

Lottie got its name from Charlotte ‘Lotte’ Reiniger, German film director and the foremost pioneer of silhouette animation.

Avoid lottie-web player

I don't recommend using the Lottie web player. The reason is that it's a heavy library (~250K) and is buggy, for instance it doesn't scale correctly according to its container. The file size can be reduced slightly by choosing one of the "lite" players but it's still a big file size in the end. Improving the file size of this library seems hard since it's not a modern library. It's not even using modules. In order for this web player to be smaller it would have to be rewritten to modern JS (or TS) and also know of the files it is going to play, so it would be able to leave out features that are not needed. (For instance music playback is part of the library, but our animations don't use that.)

Prefer converting to SMIL

There's a NPM package for converting Lottie files into native SVG animations (SMIL). If possible we recommend using that over the Lottie web player.

How to:

  • Rewrite the example to use the file name of your Lottie file and adjust the exported file name
  • Check that the animation plays back in all major browsers
  • Simply use this file from within the source code

React solution

We've made a custom React solution. Note that if you use another setup then these tips might not apply.

  • SVG placeholders for before the animation is loaded
  • The animations are imported as modules in React
  • The animation modules are loaded asynchronously
  • There's two breakpoints, one animation for narrow screens and one for wider screens

Placeholder

SVG placeholders are easy to craft. Just copy the animation file and paste into a placeholder.svg. Remove everything inside the SVG element and replace that with a rect the same dimensions as the image. For example, this is a placeholder image:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" width="820" height="461" viewBox="0 0 820 461" style="width:100%;height:100%">
    <rect fill="#ffffff" width="820" height="461"/>
</svg>

Converting SVG:s to React components

In order to allow the animation to be a module and part of React, use the following pattern:

import React from 'react';
import { ReactComponent }  from './animation.svg';

export default () => <ReactComponent />;

Responsive images

Since I'm using TypeScript to wrap the images I'm using TypeScript to detect the screen resolution and decide what to display too.

import React from 'react';
import useWindowSize from 'hooks/useWindowSize';

type Breakpoint = {
    width: number;
    component: React.FC;
};

type Props = {
    breakpoints: Breakpoint[];
};

const ResponsivePicker: React.FC<Props> = ({ breakpoints }) => {
    const size = useWindowSize();
    // Ensure that breakpoints are sorted before iterating
    breakpoints.sort((a, b) => a.width - b.width);
    for (const breakpoint of breakpoints) {
        const { width, component: Component } = breakpoint;
        if (size.width < width) {
            return <Component />;
        }
    }
    // Fall back to widest breakpoint
    const { component: Component } = breakpoints[breakpoints.length - 1];
    return <Component />;
};

export default ResponsivePicker;

I copied the useWindowSize hook from here: https://usehooks.com/useWindowSize/

Detecting IE11

The SVG files are rendered incorrectly in IE11 so we wanna hide them. This is how we detect IE:

import React from 'react';

type Props = {
    ie: React.FC;
    other: React.FC;
};

const isIE = () =>
    window.navigator.userAgent.match(/(MSIE|Trident)/);

const BrowserPicker: React.FC<Props> = ({ ie, other }) => {
    const Component = isIE() ? ie : other;
    return <Component />;
};

export default BrowserPicker;

Putting it all together

This is how I'm using placeholders, dynamically loaded modules, responsive images and a check for IE up to 11:

import React, { Suspense } from 'react';
const Medium = React.lazy(() => import('./Medium/animation'));
const Large = React.lazy(() => import('./Large/animation'));
import LargePlaceholder from './Large/placeholder';
import MediumPlaceholder from './Medium/placeholder';
import ResponsivePicker from '../ResponsivePicker';
import BrowserPicker from './BrowserPicker';

export default () => (
    <BrowserPicker
        ie={() => null}
        other={()=>
            <Suspense fallback={
                <ResponsivePicker breakpoints={[
                    { width: 820, component: MediumPlaceholder },
                    { width: 2000, component: LargePlaceholder },
                ]} />
            }>
                <ResponsivePicker breakpoints={[
                    { width: 820, component: Medium },
                    { width: 2000, component: Large },
                ]} />
            </Suspense>
        }
    />
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment