Currently Framer Motion is around 24kb gzipped and minified as a whole. With tree-shaking the motion component alone is about 22kb.
This isn't unprecedented, with Pose being around 25kb and GSAP being in the low 30s. But a low bundle size is quite (either the American or British "quite") important to me, and I've had this thought in my head for a while that it's daft that the animation part of an animation library has to load synchronously.
I've done some experiments and I think it'd be possible to crank the core library down to 10kb. The rest could either be loaded synchronously with the main bundle, but as optional bits (for instance someone who only wants to use animations). Or using import() the rest could be loaded in asynchronously.
For ease of use I'd like the main entry point to remain the same, with something like motion/lazy as a safe second entry point for people who want to crank down the bundle size.
Potential savings
Functionality: ~5kb
Functionality is loaded in via renderless components so we don't run, for instance, draggable code on components that aren't draggable.
These are already abstracted and loaded into a single function. It could be that we allow this to be configured and loaded in via the currently private MotionPluginContext API. So only the functionality used in the site is loaded at all.
A further step could be that users could dynamically import this configuration, re-rendering MotionPluginContext and its subscribers when functionality has downloaded. This could be used to defer the loading of dynamic functionality until after the initial render has finished.
Popmotion animations: ~4.5kb
At this point all animations have been abstracted behind a single function that returns a promise. As all composition also works via Promises it might work quite neatly that calls to startAnimation occuring before Popmotion has loaded in simply wait for it to load before running.
Element names: ~0.7kb
Currently motion components are created by iterating over two lists of tag names. Pose used to use a Proxy to do this without maintaining lists but I had to replace it because Googlebot would crash on Pose-powered pages. Proxy is now supported by Googlebot, which means we can support it too.
This would also theoretically reduce memory usage and start times, and allow us to support arbitrary web components out of the box.
Value conversion: ~2kb
Motion performs a step of preprocessing on animation definitions to make the unanimatable animatable.
Specifically, it reads CSS variable definitions from the DOM and makes them numbers/colors, and it detects incompatible from/tos like "100vh" -> "100px" and converts those to pixel values by measuring the DOM. This function is only called when we start an animation. This wouldn't be as neat to abstract as startAnimation as it doesn't currently live behind a Promise. But ValueAnimationControls.animate does - and maybe we can do something there to defer until this functionality is present.
Currently Framer Motion is around 24kb gzipped and minified as a whole. With tree-shaking the
motioncomponent alone is about 22kb.This isn't unprecedented, with Pose being around 25kb and GSAP being in the low 30s. But a low bundle size is quite (either the American or British "quite") important to me, and I've had this thought in my head for a while that it's daft that the animation part of an animation library has to load synchronously.
I've done some experiments and I think it'd be possible to crank the core library down to 10kb. The rest could either be loaded synchronously with the main bundle, but as optional bits (for instance someone who only wants to use animations). Or using
import()the rest could be loaded in asynchronously.For ease of use I'd like the main entry point to remain the same, with something like
motion/lazyas a safe second entry point for people who want to crank down the bundle size.Potential savings
Functionality: ~5kb
Functionality is loaded in via renderless components so we don't run, for instance, draggable code on components that aren't draggable.
These are already abstracted and loaded into a single function. It could be that we allow this to be configured and loaded in via the currently private
MotionPluginContextAPI. So only the functionality used in the site is loaded at all.A further step could be that users could dynamically import this configuration, re-rendering
MotionPluginContextand its subscribers when functionality has downloaded. This could be used to defer the loading of dynamic functionality until after the initial render has finished.Popmotion animations: ~4.5kb
At this point all animations have been abstracted behind a single function that returns a promise. As all composition also works via Promises it might work quite neatly that calls to
startAnimationoccuring before Popmotion has loaded in simply wait for it to load before running.Element names: ~0.7kb
Currently
motioncomponents are created by iterating over two lists of tag names. Pose used to use aProxyto do this without maintaining lists but I had to replace it because Googlebot would crash on Pose-powered pages.Proxyis now supported by Googlebot, which means we can support it too.This would also theoretically reduce memory usage and start times, and allow us to support arbitrary web components out of the box.
Value conversion: ~2kb
Motion performs a step of preprocessing on animation definitions to make the unanimatable animatable.
Specifically, it reads CSS variable definitions from the DOM and makes them numbers/colors, and it detects incompatible from/tos like
"100vh"->"100px"and converts those to pixel values by measuring the DOM. This function is only called when we start an animation. This wouldn't be as neat to abstract asstartAnimationas it doesn't currently live behind aPromise. ButValueAnimationControls.animatedoes - and maybe we can do something there to defer until this functionality is present.