Journal

How to Add Scroll Animations to Shopify Without an App (2026)

You can add scroll animations to a Shopify store without installing an app. Online Store 2.0 themes let you paste your own CSS and JavaScript into a Custom Liquid block or a section file, and modern browsers can animate on scroll natively — no third-party script required. Apps are convenient, but they add a render-blocking script to every page and a monthly bill for an effect you can ship in a few lines of code.

This guide covers the three methods that actually work in 2026, when to use each, exactly where to paste the code in your theme, and the performance and accessibility details that separate a polished effect from one that tanks your Core Web Vitals.

The short answer

There are three no-app ways to animate on scroll, in order of weight:

  1. Native CSS scroll-driven animations (animation-timeline) — zero JavaScript, runs off the main thread, best for fade-and-rise reveals. Supported in Chrome, Edge, and Safari 18+; Firefox needs a fallback.
  2. Intersection Observer (or the small AOS library) — a few KB of JavaScript, works in every browser, best for "fade in when it enters the viewport."
  3. GSAP ScrollTrigger — the heavyweight, for pinned sections, scrubbed timelines, and morphing effects. This is what most paid animation apps run under the hood.

Pick the lightest method that produces the effect you want. Most "make my section fade up as I scroll" requests only need method 1 or 2.

Method 1 — Native CSS scroll-driven animations (no JavaScript)

This is the 2026-native way. The browser ties an animation's progress directly to scroll position, so it runs on the compositor thread and stays smooth even on cheap phones. No library, no <script>, nothing to maintain.

There are two timelines:

  • view() — tracks an element as it enters and leaves the viewport. Use this for reveal-on-scroll.
  • scroll() — tracks how far a scroll container has been scrolled, 0% to 100%. Use this for progress bars and scrubbed effects.

A reveal-on-scroll that fades and lifts each element as it comes into view:

@keyframes reveal {
  from { opacity: 0; transform: translateY(40px); }
  to   { opacity: 1; transform: translateY(0); }
}

.reveal-on-scroll {
  animation: reveal linear both;
  animation-timeline: view();
  /* Start when the element is 20% up from the bottom edge,
     finish by the time it's 40% up the viewport. */
  animation-range: entry 0% cover 40%;
}

/* Browsers without support (Firefox today) just show the element. */
@supports not (animation-timeline: view()) {
  .reveal-on-scroll { opacity: 1; transform: none; }
}

/* Respect users who asked for less motion. */
@media (prefers-reduced-motion: reduce) {
  .reveal-on-scroll { animation: none; opacity: 1; transform: none; }
}

Then add class="reveal-on-scroll" to any element you want to animate.

Browser support, honestly: Chrome and Edge have shipped this since version 115, and Safari since 18. Firefox still keeps it behind a flag, so always include the @supports not (...) fallback above — without it, Firefox users see invisible content. With the fallback, they simply see the element with no animation, which is correct.

The two CSS blocks at the end — the @supports fallback and the prefers-reduced-motion rule — are not optional polish. The first prevents a blank page in Firefox; the second is an accessibility requirement. Skipping either is the most common way a hand-rolled scroll animation breaks.

Method 2 — Intersection Observer (works everywhere)

When you need consistent behavior across every browser including Firefox, use Intersection Observer. It's a browser API that tells you when an element scrolls into view, so you toggle a class and let CSS do the animation. It's a few lines and ships in every browser.

<style>
  .io-reveal { opacity: 0; transform: translateY(40px); transition: opacity .6s ease, transform .6s ease; }
  .io-reveal.is-visible { opacity: 1; transform: translateY(0); }
  @media (prefers-reduced-motion: reduce) {
    .io-reveal { opacity: 1; transform: none; transition: none; }
  }
</style>

<script>
  (() => {
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
    const els = document.querySelectorAll('.io-reveal');
    const io = new IntersectionObserver((entries) => {
      for (const e of entries) {
        if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); }
      }
    }, { threshold: 0.15 });
    els.forEach((el) => io.observe(el));
  })();
</script>

Add class="io-reveal" to elements you want to fade up. Note io.unobserve — it stops watching each element after it animates once, which keeps things cheap.

Prefer this over drag-and-drop libraries like AOS when you only need a couple of effects: AOS is great, but pulling in its CSS and JS for a single fade-up is more weight than the four lines above.

Method 3 — GSAP ScrollTrigger (the heavyweight effects)

CSS and Intersection Observer cover reveals. They do not comfortably do pinned sections that hold in place while content swaps, scrubbed timelines tied to scroll, or shape morphs. That's GSAP with the ScrollTrigger plugin — the same engine most paid Shopify animation apps run internally. GSAP is now fully free, including all plugins.

This is real engineering, not a snippet. You load GSAP, register ScrollTrigger, build a timeline, and tie it to a trigger element with start/end/scrub. Done wrong it causes layout thrash and jank; done right it's buttery. The honest take: if you want one pinned, scrubbed hero, budget real time to learn ScrollTrigger — or use a section that already ships it (more on that below).

A minimal pinned, scrubbed example to show the shape of it:

<script type="module">
  import gsap from 'https://cdn.jsdelivr.net/npm/gsap@3/+esm';
  import { ScrollTrigger } from 'https://cdn.jsdelivr.net/npm/gsap@3/ScrollTrigger/+esm';
  gsap.registerPlugin(ScrollTrigger);

  if (!matchMedia('(prefers-reduced-motion: reduce)').matches) {
    gsap.to('.panel', {
      xPercent: -100,
      ease: 'none',
      scrollTrigger: { trigger: '.panel-wrap', pin: true, scrub: 1, end: '+=1500' },
    });
  }
</script>

Two rules that keep GSAP fast on a store: animate only transform and opacity (never width, height, top, or margin), and put the ScrollTrigger on the timeline, not on a child tween. Break either and you'll watch your INP score climb.

Where to paste the code in Shopify

Online Store 2.0 themes (Dawn and anything built on it) give you two safe places.

Option A — Custom Liquid block (no theme files touched). Best for a one-off on a single page.

  1. In the admin, go to Online Store → Themes → Customize.
  2. On the page you want, click Add section (or Add block) and choose Custom Liquid.
  3. Paste your HTML, <style>, and <script> into the box and save.

This survives theme updates and you can delete it anytime from the editor. The trade-off: the code only lives on that one block.

Option B — A section or snippet file (reusable). Best when you want the effect available across the store.

  1. Online Store → Themes → ⋯ → Edit code.
  2. Put shared CSS/JS in a snippet (e.g. snippets/scroll-animations.liquid) and {% render 'scroll-animations' %} it where needed, or add your markup to a section's .liquid file with a {% schema %} block so merchants can toggle it in the editor.

Avoid pasting animation scripts directly into theme.liquid's <head> — that loads them on every page whether they're used or not, which is exactly the bloat you left the app to escape. Load motion only where it runs.

Always work on a duplicate of your live theme (Themes → ⋯ → Duplicate), test there, then publish. Edit code edits are immediate and there is no undo on the live store.

Don't let the animation cost you sales

Scroll animations have a reputation for wrecking performance. It's not motion itself — it's motion implemented without regard for the browser's critical path. Keep these and your Core Web Vitals stay green:

  • Animate transform and opacity only. These are GPU-composited and don't trigger layout. Animating width/height/top/left/margin forces reflow on every frame and is the number-one cause of janky scroll.
  • Reserve space so nothing shifts. An element that starts at opacity: 0 must still occupy its layout box (use opacity/visibility, never display: none), or you'll get a Cumulative Layout Shift penalty.
  • Always honor prefers-reduced-motion. Every example above does. Roughly a third of users have a reduced-motion preference set; ignoring it is both an accessibility failure and, in many regions, a compliance risk.
  • Don't animate above the fold on load. Your Largest Contentful Paint element should render immediately. Animate it in and you delay the very thing Google measures.

We go deeper on this in our own build standard — every section we ship has to pass these gates before it reaches the gallery.

So when should you still use an app, or a prebuilt section?

No-app is the right default for reveals and simple motion. But be honest about where it stops being worth it:

  • You want one fade-up across the store → Method 1 or 2. An app is overkill.
  • You want a signature, scrubbed, pinned effect and don't want to learn ScrollTrigger or maintain it through theme updates → a prebuilt section is the sweet spot: you get GSAP-grade motion as a drop-in, without a monthly app script on every page.
  • You want a whole animation builder UI for non-technical staff to compose effects → that's genuinely what apps are for. Pay for it knowingly.

This is the gap we built Shopiflame for. Each section is a single drop-in file with the GSAP effect already engineered and performance-gated — and you can try it live with your own content before you pay, with no app and no subscription. Two examples that use exactly the techniques above, done properly:

  • The Sticky Cards section — story cards that stack and swap as shoppers scroll (a pinned, scrubbed ScrollTrigger timeline, the kind Method 3 describes).
  • The Text Reveal section — headlines that assemble word by word as they enter view (a reveal effect, hardened for reduced-motion and no-JS).

FAQ

Can you add animations to Shopify without an app? Yes. Online Store 2.0 themes accept custom CSS and JavaScript through a Custom Liquid block or a section file. Modern browsers can also animate on scroll natively with animation-timeline, no script at all.

Will scroll animations slow down my Shopify store? Only if implemented carelessly. Animate transform and opacity (not layout properties), reserve space to avoid layout shift, and don't animate your above-the-fold hero on load. The native CSS method runs off the main thread and is the lightest option.

Do native CSS scroll animations work in Safari and Firefox? Safari supports them from version 18, alongside Chrome and Edge. Firefox still keeps them behind a flag, so include an @supports not (animation-timeline: view()) fallback — or use the Intersection Observer method, which works everywhere.

Is GSAP free to use on Shopify? Yes. GSAP and all its plugins, including ScrollTrigger, are free. The cost of a GSAP effect is the engineering and maintenance time, not a license.

Will my custom code survive a theme update? Code in a Custom Liquid block or in your own snippet/section files persists. Edits to core theme files can be overwritten — which is why you should keep custom motion in its own snippet and always test on a duplicated theme before publishing.