Blog / Development

Interactive graphics without slowing your site: a performance checklist

Interactive graphics rarely make a page slow because they are big. They make it slow because they load too early, run when nobody is looking or shift the layout when they appear. Here is a checklist that covers all three, with code.

By Abdullah NomanOctober 16, 20263 min read

Speedometer Gauge KPI, hairline isometric illustration

A useful way to think about the cost of an interactive graphic is to split it into four parts: downloading it, starting it up, running it every frame, and the layout shift when it appears. Each has its own fix.

A real example

One team's write-up on lazy-loading Spline 3D scenes is a good illustration. Their Lighthouse score went from 30 to 90, and Largest Contentful Paint from 5.7 to 0.6 seconds. They did not shrink the scenes. They loaded them when they came near the viewport, and showed placeholders with reserved heights until then. Their summary: the page was not slow because the assets were large, but because they loaded too early.

1. Never block the first paint

Load the script for any decorative graphic with async or defer, never as a plain script in the <head>:

<script src="/js/illustration.js" defer></script>

Also make sure the page does not hide content until the graphic is ready. The headline should paint first.

2. Load it when it comes near the screen

A graphic in the footer does not need to load with the hero. Start loading shortly before it scrolls into view:

const io = new IntersectionObserver(entries => {
  for (const e of entries) {
    if (!e.isIntersecting) continue
    io.unobserve(e.target)
    loadFigure(e.target)           // fetch the script or file, then mount it
  }
}, { rootMargin: '300px' })        // start 300px before it is visible

document.querySelectorAll('[data-figure]').forEach(el => io.observe(el))

3. Reserve the space

An empty container that grows when the graphic arrives pushes the content below it down, which counts against Cumulative Layout Shift. Give the box its final shape from the start:

.figure { width: 100%; max-width: 480px; aspect-ratio: 5 / 4; }

4. Do no work off screen or at rest

  • Pause off screen. Use an IntersectionObserver to stop animation loops when the graphic scrolls away.
  • Stop when idle. A pointer-driven graphic only needs to animate while something is moving. Stop requesting frames once it has settled.
  • Share one loop. Five graphics with five requestAnimationFrame loops do more work than one loop that updates all five.
  • Keep pointer handlers light. Record the position in the event handler; do the drawing in the next animation frame. Mark scroll and touch listeners { passive: true } when they do not call preventDefault().

5. Animate cheap properties

Changing transform and opacity is cheaper for the browser than changing sizes or positions that force layout. In SVG, moving a group with a transform is usually cheaper than rewriting many path shapes every frame. With line illustrations of a few hundred elements this rarely matters; with thousands, test on a mid-range phone.

6. Count the graphics

Even cheap graphics add up. One or two interactive pieces per screen is a sensible ceiling, for performance and for attention.

7. Measure

  • Lighthouse or PageSpeed Insights for load (LCP) and layout shift (CLS), with and without the graphic.
  • The Performance panel in Chrome DevTools while you move the pointer over the graphic. Look for long tasks and dropped frames.
  • Interaction to Next Paint (INP) in field data. A graphic that runs heavy work on every pointer move can delay clicks elsewhere on the page.

How Liniqo Live handles this

We built Liniqo Live's embed around this checklist, so you get it without writing it:

ConcernWhat the embed does
BlockingOne shared script, about 35 KB (roughly 12 KB gzipped), loaded with async
DownloadEach figure is its own 4 to 6 KB file, fetched only when its box comes within about 300 px of the screen
Layout shiftFigures draw into a 5:4 box; give the container aspect-ratio: 5 / 4 and nothing moves
Running costAll figures share one animation loop, which only updates figures on screen and stops when nothing is moving
Duplicate scriptsLoading embed.js twice is harmless; the second copy does nothing
Live figure: move your pointer across it, or drag a finger on a phone. CDN nodes is one of 23 free interactive figures.

The figure above loaded when you scrolled near it. Find others in the library; 23 are free, all 500 are on Pro and Lifetime. For still SVGs in React, see SVG illustrations in React and Next.js.

Questions

Do animations hurt Core Web Vitals?

They can. Scripts that block rendering delay LCP, graphics that appear without reserved space cause CLS, and heavy work on pointer events can hurt INP. Async loading, reserved space and light handlers avoid all three.

How do I lazy-load an animation or 3D scene?

Observe its container with IntersectionObserver and a rootMargin of a few hundred pixels, and only fetch and start the scene when it intersects. Show a placeholder of the same size until then.

Should I use async or defer for an illustration script?

Either is fine for a decorative graphic. Use async when the script does not depend on other scripts, defer when it needs to run in order after the document is parsed.

How many interactive graphics can a page have?

Technically many, if they load lazily and pause off screen. For attention, one or two per screen is a good limit.

Keep reading