Lottie and Vector Animation for the Web, Done Properly
Follow an illustrative onboarding project from brief to handover to see how to design, export, optimize and ship Lottie animation that stays light and smooth.
Lottie and vector animation for the web means animation that ships as vector data, usually a JSON file describing shapes, paths, colors and keyframes, and is drawn by the browser at runtime through a small player library, rather than being delivered as a video file or a stack of image frames. Because the browser draws it, the animation stays crisp on a phone, a 4K monitor and everything in between, it is typically far smaller than the equivalent video, and code on the page can pause it, scrub it, speed it up or recolor it on the fly.
That combination makes it attractive to product teams, marketers building landing pages and anyone designing onboarding, empty states or explainer illustrations. It also makes it easy to misuse. Not every After Effects effect survives export, a complex file can be expensive for a mid-range phone to render, and there are plenty of cases where a few lines of CSS or a short video would be lighter and simpler. The difference between a delightful result and a sluggish, oversized one is almost entirely in decisions made before and after the animating itself.
This article follows one project from brief to delivery to show those decisions in context. The project is an illustrative example, a composite built to show a realistic workflow; the client, the files and every number attached to them are invented for teaching purposes and are not measurements from a real engagement. Where a figure depends on your own tools and content, we say what drives it rather than presenting it as a fact.
The brief: six onboarding animations for a budgeting app (illustrative)
Our illustrative client is a small company with a personal budgeting web app. Its product team wanted to rework the first-run experience: a welcome screen, three onboarding steps explaining how the app categorizes spending, a success state when the user connects a bank account, and an empty state for the transactions list. Their designer had produced static illustrations in a flat, geometric style, and the brief asked for "those illustrations, but alive." The team had heard that Lottie was the standard answer and asked for six Lottie files.
The first useful thing we did was not to accept the deliverable list at face value. A request for "six Lottie files" describes a solution, not a need. The real requirements, once we talked them through, were these: the onboarding needed to feel polished and on brand, it had to load quickly on the mobile web because most sign-ups came from phones, the brand colors were about to change in a planned refresh so anything we built should be easy to recolor, and the engineering team was small and did not want a heavy dependency they would have to maintain.
Those four requirements shaped every later decision. Fast mobile loading put a ceiling on file size and render cost. Easy recoloring pointed toward vector output with named layers that code could target. The small engineering team argued for the lightest integration that would do the job, and for using plain CSS wherever the motion was simple enough not to need an animation file at all.
Decision: We reframed the deliverable from "six Lottie files" to "six onboarding moments, each built in whichever format is lightest for the motion it needs." The client agreed to judge the work on load weight, smoothness on a mid-range phone and ease of recoloring, not on file format.
We also agreed on the review process up front: two rounds of feedback on motion style using short previews, a single round on timing once animations were in the real page, and a clear point at which the design was frozen before optimization began. Getting review mechanics settled early matters more with web animation than with video, because a change after export means re-exporting, re-optimizing and retesting. Our approach to reviewing animation with clients without endless revision loops covers this in more depth.
Sorting each moment into Lottie, CSS or video
Before opening any animation software, we went through the six moments one at a time and asked what kind of motion each one actually needed. This triage is the single most valuable step in a web animation project, because the most common mistake is reaching for an animation file when the motion is simple enough for the browser to handle natively.
The decision criteria we use are straightforward. If the motion is a property change on an element that already exists in the page, such as a button scaling, a card sliding in, a checkmark stroke drawing or a progress bar filling, it belongs in CSS transitions, CSS keyframe animations or the Web Animations API. These run with no extra library, can often be handled by the browser's compositor, and are trivially easy for developers to adjust. The MDN Web Docs reference for the Web Animations API shows how the same keyframe model that CSS uses can be driven from JavaScript with play, pause, reverse and playback-rate control, which covers most of what people want from "runtime control."
If the motion is an illustration, meaning many shapes deforming, morphing, drawing on and moving in coordinated ways that would be painful to hand-code, a vector animation file is usually the right tool. If the motion is photographic, involves real footage, heavy texture, grain, blur, lighting or thousands of particles, a short compressed video is almost always simpler and lighter than trying to force it into vectors.
| Onboarding moment | Motion needed | Format chosen | Main reason |
|---|---|---|---|
| Welcome screen | Character illustration, coin and chart shapes assembling, gentle idle | Lottie | Complex coordinated shape animation, must recolor after rebrand |
| Step 1: connect accounts | Two cards linking with an animated connector line | Lottie | Path morphing and line drawing across several shapes |
| Step 2: automatic categories | Receipts sorting into colored buckets | Lottie | Many objects moving on staggered paths |
| Step 3: set a goal | A progress ring filling to a target | CSS / SVG stroke animation | A single stroke-dashoffset change on an existing SVG |
| Bank connected success | Checkmark draw and a short burst | Lottie | Burst particles plus draw-on, short and played once |
| Empty transactions list | Illustration fading and floating in | CSS on a static SVG | Only opacity and transform; no shape change needed |
Two of the six moments dropped out of the Lottie list immediately. The progress ring on step 3 is a circle whose stroke fills; that is one property animating on one SVG element, and building it as a Lottie file would have added a network request and a parse step for no benefit. The empty state only needed its existing static illustration to fade and float into place, which is a transform and opacity animation the browser handles cheaply.
We also briefly considered video for the welcome screen, because the designer had imagined a soft glow behind the character. Video would have handled the glow beautifully but would have ruled out recoloring, looked soft on high-density screens at large sizes, and weighed more. We kept the glow idea but rebuilt it as a flat radial gradient shape, which exports cleanly.
Designing within the feature set that actually exports
The mistake that costs the most time on Lottie projects is designing with effects that will not export and discovering it at handover. After Effects is an enormous toolset, and the Lottie format and its players only support a subset of it. An animator can spend days building something beautiful from blurs, glows, displacement effects, third-party plugins and 3D camera moves, then export it and find half of it missing or rendered differently in the browser.
We prevent that with a short export test before any real animation begins. On this project the designer's style frames used flat fills, a few linear and radial gradients, rounded strokes, a drop shadow under the character and a subtle noise texture across the background. We built a ten-second test composition containing one example of each: a shape layer with a gradient fill, a trimmed path drawing on, a masked element, a precomposition with a track matte, a drop shadow, the noise texture and a text layer. We exported it and played it in the browser player the engineering team intended to use.
The results sorted the style into three groups. Flat fills, strokes, trim paths, gradients and transform animation came through faithfully. The masked element and the matte rendered correctly but, as expected, added rendering cost, so we noted to use them sparingly. The drop shadow and the noise texture did not come through in a usable way, and the text layer depended on font handling we did not want to rely on. Support for effects also varies between players, so a file that looks right in one web player is not guaranteed to look identical on native mobile players; testing in the actual target player is the only reliable check.
Trap avoided: Designing with effects that will not export and finding out at handover. We caught three problems in a ten-second test on day three instead of in week five:
- The drop shadow was redrawn as a flat, semi-transparent shape offset below the character, which reads the same at small sizes.
- The noise texture was dropped; embedding it as a raster image would have bloated the file and blurred at large sizes.
- All text was converted to shapes, since onboarding copy lives in the page HTML anyway, where it can be translated and read by screen readers.
The rules that came out of the test went into a one-page style guide for the animators: shape layers only, no raster images, no layer effects apart from the ones we had verified, gradients allowed but limited to two stops where possible, masks and mattes only where there was no simpler construction, no expressions, and every color defined on a named fill so it could be found later. That last rule served the recoloring requirement, and it is much easier to enforce from the start than to retrofit.
If your team designs in a different tool, the same principle holds. Adobe's own Help Center documentation on exporting animation for the web lists what each web output supports, and it is worth reading before the style is locked rather than after. For projects built in Adobe Animate rather than After Effects, our guide to HTML5 Canvas animation with Adobe Animate covers the equivalent constraints for that export path.
Building the animations with the export in mind
With the constraints agreed, the animation itself proceeded much as any motion design project does: rough timing passes, refinement of easing, secondary motion, and polish. But several construction habits make a large difference to the quality and weight of the final file, and we applied them throughout.
Keep the layer structure lean
Every layer, group and shape in the composition becomes data in the JSON and work for the player on every frame. Illustrations imported from design tools often arrive with dozens of redundant groups, hidden layers and shapes that are fully covered by other shapes. Before animating the welcome screen, we cleaned the imported artwork: merged shapes that never move independently, deleted anything invisible, and flattened unnecessary nesting. On the illustrative welcome file, the imported illustration had roughly twice as many shape groups as the cleaned version, with no visible difference.
Animate transforms before paths
Moving, scaling and rotating a group is cheap to describe and cheap to render. Animating the individual vertices of a path, so that a shape morphs, stores a full set of points for every keyframe and asks the renderer to rebuild the shape each frame. We used path animation where the motion genuinely needed it, such as the connector line on step 1 bending as the cards linked, and used transforms everywhere else.
Be deliberate with keyframes and frame rate
Baked keyframes, where a tool writes a keyframe on every single frame, inflate file size dramatically. We kept animation on sparse keyframes with easing curves doing the work between them. We also chose the composition frame rate deliberately. Lottie players interpolate between keyframes, so the composition frame rate mainly affects how keyframes snap and how any frame-by-frame elements behave; the rendered smoothness depends heavily on the device. We built at 30 fps, which suited the flat illustrative style and kept any stepped animation compact. Our piece on frame rates for web animation goes into when 24, 30 or 60 fps makes sense.
Name everything that code might touch
Because the brand refresh was coming, every fill and stroke that used a brand color was placed on a layer with a predictable name, such as brand-primary, brand-accent and neutral-dark. That naming convention is what later let developers swap colors at runtime or batch-edit them in the JSON without opening After Effects. We also named the segments of each animation with markers, such as intro, idle and outro, so that code could play specific ranges instead of guessing at frame numbers.
Design loops to be short, calm and stoppable
The welcome screen needed an idle state after its intro, because users sometimes linger. We designed a four-second idle loop with small, slow movements confined to a few elements, rather than having the whole illustration keep moving. A calm idle is easier on users who find motion distracting, and it keeps render cost low if the loop does run for a while. Crucially, we planned from the start that the idle loop would stop after a few cycles and would not play at all for users who prefer reduced motion.
A six-week schedule from brief to handover
The project ran over six weeks of calendar time, with the animation effort itself concentrated in the middle. The front-loading of testing is deliberate: an hour spent in week one proving what exports saves days later.
- Week 1 Brief workshop, requirements agreed, triage of six moments into Lottie, CSS and static formats, player library chosen provisionally.
- Week 2 Style frames refined, ten-second export test run in the target player, animator style guide written, artwork cleaned and restructured.
- Week 3 Rough motion passes for the four Lottie animations, shared as browser previews for the first review round.
- Week 4 Refinement, secondary motion and idle loop; second review round; design frozen at the end of the week.
- Week 5 Export, optimization, integration into a staging build, CSS animations for the progress ring and empty state built alongside.
- Week 6 Device testing on a mid-range phone, reduced-motion checks, one timing adjustment round in context, source files and documentation handed over.
One detail worth noticing is that the reviews in weeks 3 and 4 used browser previews of real exports rather than rendered MP4s from After Effects. Reviewing an MP4 shows the client what After Effects renders, which may not be what the browser renders. Reviewing the actual export in the actual player means that what gets approved is what ships.
Exporting and optimizing the JSON rather than shipping the raw export
The raw file that comes out of an exporter is rarely the file you should ship. Exporters prioritize fidelity and completeness, and they include data that is useful for editing but unnecessary for playback. On this project, optimization was a planned stage with its own time in the schedule, not something squeezed in at the end.
We treated the raw export as an intermediate file, never a deliverable. Each animation went through the same optimization pass, and we recorded the before-and-after size so the client could see where the weight went.
The optimization pass for each file followed the same order:
- Check for embedded images. Any raster image in the composition is exported as either a separate file or a base64-encoded string inside the JSON, and embedded images are the most common cause of a surprisingly large file. Our style guide had banned them, but we checked every export anyway and found one stray bitmap left over from a reference layer.
- Remove unused and hidden content. Guide layers, disabled layers and unused precompositions sometimes make it into the export depending on settings. We confirmed the export settings excluded them and inspected the layer list in the JSON.
- Reduce numeric precision. Exported coordinates and values often carry many decimal places. Trimming them to two or three decimal places is visually invisible at web sizes and removes a meaningful share of the file's characters. We compared before and after at the largest display size to confirm nothing shifted.
- Simplify paths. Shapes traced or imported from illustration tools can carry far more vertices than their curves need. We simplified the heaviest paths in the source file and re-exported, rather than trying to edit points in the JSON.
- Trim keyframes. We checked for accidental baked keyframes and removed redundant keyframes that held the same value.
- Compress for delivery. JSON is text and compresses well, so we confirmed the server applied gzip or Brotli to the files. We also evaluated the dotLottie container format, which zips the animation and can bundle several animations and themes into one file, as an alternative for the future.
In the illustrative project, the welcome animation went from 412 KB as exported to 118 KB after optimization, and transferred at roughly 31 KB once the server compressed it. The biggest single saving was the stray embedded bitmap; the next largest came from precision reduction and path simplification. Those proportions will differ on every file, which is exactly why it is worth measuring each one rather than assuming.
Trap avoided: Ignoring the player library's own weight. The animation files were small after optimization, but the player that renders them is JavaScript the browser must download, parse and execute too. For a single small animation, the library can outweigh the animation by a wide margin. We measured the library as the page would actually load it and counted it in the budget alongside the JSON files.
Choosing a player library and renderer
Lottie files do nothing on their own; a player library reads the JSON and draws it. The choice of player affects bundle size, rendering performance, feature support and how much control developers have. We compared the options against the client's constraints rather than defaulting to whatever a tutorial used.
| Option | Strengths | Trade-offs | Fit for this project |
|---|---|---|---|
| Full lottie-web build, SVG renderer | Broad feature support, sharp output, layers addressable in the DOM | Largest bundle; many SVG nodes can be slow for dense animations | Good fidelity, heavier than needed |
| lottie-web light build, SVG renderer | Smaller bundle, same renderer, enough for files without expressions | No expression support, so files must be built without them | Chosen: our style guide already banned expressions |
| lottie-web, canvas renderer | Often faster for many shapes; one element in the DOM | Must handle device pixel ratio for sharpness; layers not in the DOM | Kept as fallback for the heaviest file |
| WebAssembly-based players (for example dotLottie players) | Consistent rendering engine across platforms, dotLottie support | Different loading characteristics; worth testing on target devices | Evaluated for the next phase |
| No library: CSS and Web Animations | Zero dependency, compositor-friendly for transforms and opacity | Only practical for simple motion on existing elements | Used for the progress ring and empty state |
Because we had designed without expressions from the start, the lighter build of the player was an option, and it met every feature need in the test file. This is a good example of how early constraints pay off later: a style guide decision made in week two directly reduced the JavaScript shipped in week five.
The renderer choice is subtler. The SVG renderer draws each shape as an element in the page, which keeps output sharp and lets developers inspect and style individual layers, but a file with hundreds of shapes creates hundreds of elements that the browser must update every frame. The canvas renderer draws everything into a single bitmap surface, which can be cheaper for dense scenes but requires care with high-density screens so the output does not look soft. We defaulted to SVG and kept canvas in reserve for any animation that failed the performance test.
We also loaded the player lazily. The onboarding flow did not need it until the welcome screen rendered, so the library was fetched as a separate chunk only when the onboarding route loaded, and each animation file was requested only when its step was about to be shown. Nothing in the main application bundle grew.
Testing rendering performance on a mid-range phone
Animations that feel perfectly smooth on a designer's laptop can stutter on the phones most visitors actually use. Rendering vector animation is real work for the device: every frame, the player computes shape positions, rebuilds paths, and the browser paints the result. Testing on a flagship phone or a desktop tells you very little about the typical experience, so we test on a mid-range Android device a few years old, which is closer to what a large share of mobile visitors carry.
The web.dev guidance on animation performance explains the underlying mechanics: the browser can move and fade layers cheaply on the compositor, while anything that forces layout or repaints large areas each frame is expensive. Vector animation rendered through SVG or canvas usually involves painting, so the questions are how much is being painted, how often, and while what else is happening on the page.
What we measured
- Dropped frames during playback. We recorded performance traces in the browser's developer tools while each animation played, using a remote debugging connection to the phone, and looked for long frames and gaps.
- Main-thread time during the first seconds of the page. The onboarding screen also had to respond to taps immediately. An animation that parses and renders while the user is trying to tap "Continue" makes the interface feel broken even if the animation itself is smooth.
- Time to first frame. The gap between the screen appearing and the animation starting, which includes downloading the library and file, parsing the JSON and building the scene.
- Battery and heat during the idle loop. Less precise, but we left the welcome screen idling for a few minutes and checked that the phone did not warm noticeably.
Three of the four Lottie animations passed without changes. The receipts-sorting animation on step 2 did not: with a dozen receipts moving at once, each built with a masked shadow, it dropped frames noticeably on the test phone. Switching that one file to the canvas renderer helped a little. What fixed it properly was going back to the source and removing the masks, redrawing each receipt's shadow as a simple offset shape, and reducing the number of receipts moving simultaneously from twelve to eight. Visually, nobody in the review could tell the difference; on the phone, it became smooth.
Trap avoided: Complex vector animation running continuously on a page. Even a smooth animation costs CPU and battery for as long as it plays. None of the six moments loops indefinitely: intros play once, the idle loop stops after three cycles, and every animation pauses when it scrolls out of view or the browser tab is hidden.
Pausing offscreen animation deserves emphasis. We used an IntersectionObserver so each animation played only while visible and paused when the user moved to another step, and we listened for the page visibility change so nothing kept rendering in a background tab. These are a few lines of integration code, and they are the difference between an animation that costs something only while someone is watching it and one that costs something all the time.
Respecting reduced motion and other accessibility needs
Some people experience dizziness, nausea or distraction from on-screen motion, and operating systems let them say so through a reduced-motion setting. Browsers expose that preference through the prefers-reduced-motion media query, which can be read from CSS and from JavaScript with matchMedia. Respecting it is not optional polish; it is part of doing web animation properly.
A reduced-motion experience should not simply delete the animation and leave a blank space. For each moment, we designed a deliberate reduced-motion state:
- Welcome screen: the player loads the animation and jumps straight to a designed final frame, the fully assembled illustration, without playing the intro or idle loop. Because we had placed markers, this was a single call to go to the end of the intro segment and stop.
- Onboarding steps 1 and 2: the same approach, showing a composed still frame that communicates the idea on its own.
- Bank-connected success: the checkmark appears fully drawn with no burst. The confirmation also appears as text, so no one depends on the animation to understand what happened.
- Progress ring and empty state: the CSS animations are wrapped in the media query, so the ring shows its final value and the illustration appears in place.
Beyond reduced motion, we treated every animation as decorative from the perspective of assistive technology, because the meaning of each screen lived in the HTML text beside it. The animation containers were hidden from screen readers so they did not announce a meaningless graphic, and no instruction or status relied on the animation alone. We also checked that nothing flashed, and that the idle loop and every other animation lasting more than a few seconds could be stopped, which our "stops after three cycles" rule and the pause-on-scroll behavior already ensured.
The principle to take away is simple: a reduced-motion version is a designed state, not a deleted animation. Pick the frame that tells the story on its own, and show that.
Integration, runtime control and recoloring after the rebrand
Runtime control is one of the main reasons to choose vector animation for the web over video, and this project used it in three ways.
First, segments. Because the animators had placed markers named intro, idle and outro, the developers could play the intro once, loop the idle a fixed number of times, and play the outro when the user tapped "Continue," all without hard-coding frame numbers. If the animation was retimed later, the markers moved with it and the code kept working.
Second, interaction. The success animation needed to wait until the bank connection actually confirmed, which could take anywhere from under a second to several seconds. We built a short looping "waiting" segment at the start of that file; the page played it while waiting and switched to the checkmark segment the moment the confirmation arrived. A video could not have done this without awkward cuts.
Third, color. Two months after launch, in our illustrative timeline, the brand refresh arrived and the primary color changed. Because every brand-colored fill sat on a consistently named layer, a developer wrote a small build script that read each JSON file, found the named fills and replaced their color values with the new palette. No animation was reopened, re-exported or re-optimized. Some players also support theming or runtime color overrides directly, and the dotLottie format can package multiple color themes with a single animation, which is worth considering when a product supports light and dark modes.
The CSS-based pieces benefited from the same planning. The progress ring used CSS custom properties for its colors, so it updated with the rest of the site's design tokens automatically. For simple interface motion, this is a strong argument for CSS and the Web Animations API: the motion inherits the design system for free.
If you are commissioning this kind of work, it helps to know what a studio is doing when it builds a library of interface animations rather than one-off files. Our web animation service covers Lottie, CSS and canvas work together for exactly that reason, since the right answer for a product is almost always a mix of formats.
Handover, documentation and archiving the source
A web animation project is not finished when the files play on the page. The client's team will need to change them, and without the right handover, every future change becomes a small rescue project.
The handover package for this project contained the After Effects project files with all artwork, organized with the same layer naming used in the export; the optimized JSON files and the raw exports they came from, so the optimization could be repeated; the one-page animator style guide listing the verified features and banned effects; a short integration note listing each file's markers, loop behavior, reduced-motion frame and the recoloring script; and a record of the performance test results with the device used. Our practical guide to delivering animation source files goes through the folder structure and naming conventions we use.
We also recorded the exporter and player versions used. Lottie exporters and players are actively developed, and a file exported with one version can render slightly differently with another. Writing down the versions means a future developer can reproduce the original result, or knows to retest if they upgrade. For longer-term storage of the project itself, the approach in archiving animation projects applies: keep the source, the fonts and any linked assets together, and test that the archive actually reopens.
Finally, we walked the client's designer through the style guide so that future illustrations would be built exportable from the start. The most durable outcome of a project like this is often not the animations themselves but a team that knows which effects to avoid.
What the finished project measured, and what we would do differently
At the end of week six, the illustrative project shipped six onboarding moments. The numbers below are part of the illustrative example and describe this invented project only; they are included to show which measurements are worth recording, not as benchmarks for your own work.
The things that went well were the things planned early: the triage that kept two moments out of Lottie entirely, the ten-second export test that caught the shadow and texture problems in week two, the layer naming that made the rebrand a script rather than a rework, and the decision to review real exports in the real player.
Two things we would do differently. First, we would have run the mid-range phone test on a rough version of the receipts animation in week three, rather than on the finished file in week six. The problem was structural, a dozen masked objects moving at once, and it was visible in the rough pass; catching it then would have saved a round of rework on polished animation. Second, we would have measured the player library's cost in the real application bundle during week one. We had estimated it from documentation, which turned out to be close, but a measured number from the start is better than an estimate.
When vector animation is the wrong answer
Because this walkthrough ended with four Lottie files, it is worth being clear about when the answer should be no. If the motion is a simple interface transition, use CSS or the Web Animations API. If the content is footage, photographic texture, heavy blur or dense particles, a short, well-compressed video with a poster frame will be simpler and lighter. If there is only one small animation on a page, weigh whether the player library is worth loading for it, or whether an animated SVG with CSS would do. And if the animation needs complex interactive states, such as a character reacting to many different inputs, look at tools designed around state machines rather than timeline playback.
For related work, isometric animation is a common style for product and onboarding illustrations and translates well to vector formats when built with the same constraints. If you are weighing outside help for a project like this, our guide to choosing an animation studio lists the questions that separate studios that understand web delivery from those that only render video. The signs you need that help are the ones this project illustrated: animations that will not export cleanly, performance that suffers on mobile, or a product that needs a coherent library of interface animations rather than a few one-off files.
Verdict Lottie and vector animation for the web earns its place for illustrative, recolorable, controllable motion, provided it is designed inside the exportable feature set, optimized after export, counted together with its player library, tested on a mid-range phone, paused when unseen and given a designed reduced-motion state. For everything simpler, CSS wins; for anything photographic, video does.
Where this comes from
- MDN Web Docs — Web Animations API
- web.dev — Animation performance
- Adobe Help Center — Export for web animation
The figures and practices above come from the sources listed.
Working on something like this?
We take on Motion Graphics & Animation work for teams who want it done once, properly. Tell us what you are building and we will tell you honestly whether we are the right studio for it. Start a project.
Where to go next
Spotted something wrong? Report an error on this page. We correct on the page and say what changed.