Lazy loading animation data would be very handy
EliotSlevin opened this issue · 8 comments
This might be far beyond the scope of this library - but being able to load animation data when it enters the viewport would be great.
Including animation data via import
is good, but if you've got quite a few complex animations on a page, this can increase your bundle size real fast. Particularly if inline bitmaps are part of an animation. I'm working on a project where our average lottie json is ~1mb, and we've got like 6 per page. That's a chunky bundle! A chunky bundle that's getting in the way of my performance scores!
A lazyload feature, built into a library like this could be hella handy. Something like
<Lottie
loop
play
animationData={'./some/static/path.json'}
lazyLoad
placeholder={placeholder.svg}
/>
The Interaction Observer API would be perfect to check if the lottie is in viewport - then if it is, fetch the json and fire up the animation.
Would be very nice.
Hi. I think you can solve the bundle size with code splitting:
const MyComponent = () => {
const [animationData, setAnimationData] = useState();
useEffect(() => {
import('./animation.json').then(setAnimationData);
}, []);
if (!animationData) return <div>Loading...</div>;
return <Lottie animationData={animationData} />;
}
See https://reactjs.org/docs/code-splitting.html
Then you can mount this component only when it enters the view. Can be achieved with something like this:
https://github.com/olistic/react-use-visibility
Just if anybody ends up finding this in the future, I solved this problem just as you suggested, with a component like this.
import React, { useEffect, useState } from 'react'
import Lottie from 'react-lottie-player'
const Animation = ({animationName, placeholder}) => {
const [animationData, setAnimationData] = useState()
useEffect(() => {
import(`./lotties/${animationName}`).then(setAnimationData)
}, [])
if (!animationData) return <img src={placeholder}/>
return <Lottie animationData={animationData} play loop/>
}
export default Animation
When the component first mounts, it renders the SVG placeholder I've passed through - all in nice small bundle. At this point the user can start interacting with the page, then it starts loading the animation data, then renders it once it's all available.
I've found that just doing that is enough to dramatically improve the performance score. Detecting what Animations
were in the viewport and then loading just those files just wasn't needed for my project.
Thanks for the help and banging library!
Just if anybody ends up finding this in the future, I solved this problem just as you suggested, with a component like this.
import React, { useEffect, useState } from 'react' import Lottie from 'react-lottie-player' const Animation = ({animationName, placeholder}) => { const [animationData, setAnimationData] = useState() useEffect(() => { import(`./lotties/${animationName}`).then(setAnimationData) }, []) if (!animationData) return <img src={placeholder}/> return <Lottie animationData={animationData} play loop/> } export default Animation
When the component first mounts, it renders the SVG placeholder I've passed through - all in nice small bundle. At this point the user can start interacting with the page, then it starts loading the animation data, then renders it once it's all available.
I've found that just doing that is enough to dramatically improve the performance score. Detecting what
Animations
were in the viewport and then loading just those files just wasn't needed for my project.Thanks for the help and banging library!
@EliotSlevin Ever encountered this error? Whenever I use your code nothing loads. JSON files work just fine without lazy-loading and I have tried multiple JSONs from LottieFiles
import React, { useEffect, useState } from 'react';
import Lottie from 'react-lottie-player';
const Hero = () => {
const [animationData, setAnimationData] = useState();
useEffect(() => {
import(`../../lottie/woman.json`).then(setAnimationData);
}, []);
if (!animationData) return <div>Loading...</div>;
return <Lottie animationData={animationData} play loop />;
};
export default Hero;
Keep getting SVG errors in console:
Edit: Resolved it by using the below
import React, { useEffect, useState } from 'react';
import Lottie from 'react-lottie-player';
const Hero = () => {
const [animationData, setAnimationData] = useState();
useEffect(() => {
import(`../../lottie/woman.json`).then((res) => setAnimationData(res.default));
}, []);
if (!animationData) return <div>Loading...</div>;
return <Lottie animationData={animationData} play loop />;
};
export default Hero;
I just want to add that it seems the issue @Skarian mentioned above is introduced with v1.3.2. I tried switching back to v1.3.1 and the error was gone. Thanks for the including the code to resolve it!
Thanks for reporting @nathanielwood
I can confirm this is an issue with the cloneDeep addition in 1.3.2, I opened #37
This will be fixed in 1.3.3, so no longer need to use .default
on imported json after that
maybe use something like merge-anything? that seems pretty stable and doesn't introduce as many security risks as lodash. I'm happy to pr this in if it helps
this one? https://github.com/mesqueeb/merge-anything
I believe it is not the same as cloneDeep, or is it? I think lodash.cloneDeep is quite safe as it is just a single function, and it has a lot more downloads/users:
there are high security warnings for anything lodash due to the way it works (https://snyk.io/vuln/SNYK-JS-LODASH-450202). its going to make it harder for consumers if you use lodash, since they will constantly have to do updates. we've found this on virtually every project that uses lodash. if you check out the pr I raised (#40), everything still works fine.