Motion Runtime
Every Shopiflame section shares a single runtime snippet — snippets/forge-motion.liquid. This file handles GSAP loading, reduced-motion detection, and the forge-js gate that keeps sections readable without JavaScript.
GSAP lazy-load
GSAP is loaded once, on the first section that needs it, via a module-type script:
{% unless forge_gsap_loaded %}
<script type="module">
import gsap from 'https://cdn.skypack.dev/gsap@3';
import ScrollTrigger from 'https://cdn.skypack.dev/gsap@3/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
window.__forge = { gsap, ScrollTrigger };
document.dispatchEvent(new CustomEvent('forge:ready'));
</script>
{% assign forge_gsap_loaded = true %}
{% endunless %}
The forge_gsap_loaded Liquid variable ensures GSAP is only requested once per page, even if multiple Shopiflame sections are present. Each section then listens for forge:ready before initializing its own animation timeline.
prefers-reduced-motion
Every section checks window.matchMedia('(prefers-reduced-motion: reduce)') before building its timeline. When the preference is set:
- The section's final visual state is applied immediately (no transition)
- No ScrollTrigger is registered
- GSAP may still load (for other sections), but the per-section animation is skipped entirely
This is not a degraded experience — the content is fully readable and the layout is identical. Motion is an enhancement, not load-bearing.
The forge-js gate
Sections add a forge-js class to their root element from inline JavaScript:
<div class="sf-section" id="{{ section.id }}">
<script>
document.currentScript.closest('.sf-section').classList.add('forge-js');
</script>
<!-- section content -->
</div>
CSS rules prefixed with .forge-js apply only when JS has run. Without it, the section renders its static fallback — a fully styled, readable layout with no motion dependencies. Content is never hidden behind a JS gate.
CLS and LCP considerations
- Sections declare explicit
aspect-ratioormin-heighton their root elements to prevent layout shift as motion initializes. - Images use
loading="eager"for above-the-fold heroes andloading="lazy"elsewhere. - The GSAP script is
type="module", which defers automatically — it never blocks rendering.
See Core Web Vitals for Shopify Motion for a deeper breakdown.
