GSAP extension
@unseenco/backstage/gsap bridges GSAP tweens and timelines to Backstage sequence time mode (v1). Register animations at runtime, then place them on the sheet sequence like keyframed props. Page mode (scroll-driven sequencer) and scroll drivers are documented in Sheet sequence modes. ScrollTrigger instances can be registered for read-only visualization on the page-mode sequencer (vertical scroll on the configured scroller, default document).
Studio support for GSAP clips is built into @unseenco/backstage/studio. You do not call studio.extend() for GSAP.
Install
yarn add @unseenco/backstage @unseenco/backstage/studio gsap @unseenco/backstage/gsapPeer dependencies: gsap (≥3), @unseenco/backstage. @unseenco/backstage/studio is optional at runtime but required for authoring.
Quick start (time mode)
Typical setup:
configureBackstageGsap()— outline namespace for proxy objects (defaultGSAP).registerGsapAnimation()— bind a paused tween or timeline to a sheet; creates outline entries underGSAP / ….attachGsapSequenceBridge(sheet)— drive registered animations from the sequence playhead.- In Studio — Add to sequence at playhead on a GSAP outline object (context menu, detail panel, or clip title menu).
import gsap from 'gsap'
import {
createRafDriver,
getProject,
setCoreRafDriver,
} from '@unseenco/backstage'
import studio from '@unseenco/backstage/studio'
import {
attachGsapSequenceBridge,
bindGsapTickerToRafDriver,
configureBackstageGsap,
registerGsapAnimation,
} from '@unseenco/backstage/gsap'
const rafDriver = createRafDriver({name: 'gsap-master-clock'})
setCoreRafDriver(rafDriver)
configureBackstageGsap({
namespace: 'GSAP',
outlineNamespace: {defaultCollapsed: false},
})
studio.initialize({__experimental_rafDriver: rafDriver})
const sheet = getProject('My project').sheet('Main')
attachGsapSequenceBridge(sheet)
bindGsapTickerToRafDriver(rafDriver, gsap)
void getProject('My project').ready.then(() => {
const tween = gsap.to('.box', {x: 100, duration: 1, paused: true})
registerGsapAnimation(tween, sheet, {label: 'Box move'})
})GSAP as the master clock
When GSAP should own timing (common for mixed GSAP + Backstage setups), use bindGsapTickerToRafDriver(rafDriver, gsap) after setCoreRafDriver(). It forwards gsap.ticker time (seconds) to rafDriver.tick() (milliseconds, like performance.now()).
To silence the one-time integration warning (for example in tests), pass suppressGsapTickerRafWarning: true to configureBackstageGsap().
See the playground demo below for a full example with DOM targets and nested labels.
Sheet time mode semantics (default timeline, playback, audio): Sheet sequence modes.
Page mode (scroll-driven sequence)
Enable page mode on the sheet and the GSAP bridge together:
import {getProject} from '@unseenco/backstage'
const sheet = getProject('My project').sheet('Main', {
sequenceMode: 'page',
gsap: true,
})gsap: true turns on attachGsapSequenceBridge and native document scroll sync when no custom scrollDriver is set. You can also call sheet.setSequenceMode('page'), attachBackstagePageScroll(sheet), and attachGsapSequenceBridge(sheet) separately.
GSAP-specific notes in page mode:
- GSAP clip
defaultDurationshould be set in percent when adding clips (defaults to 10 if omitted in page mode, not tween seconds).
Core scroll wiring (configureBackstagePageScroll, Lenis, custom ScrollDriver, overflow elements): Sheet sequence modes — page mode.
GSAP page scroll + ScrollTrigger defaults
When using ScrollTrigger with a non-document scroller, align Backstage and GSAP:
import {configureBackstagePageScroll} from '@unseenco/backstage'
import {configureBackstageGsap} from '@unseenco/backstage/gsap'
configureBackstagePageScroll({scroller: document.documentElement})
configureBackstageGsap({
pageScroll: {
scroller: document.documentElement,
// applyScrollTriggerDefaults: true — optional; sets ScrollTrigger.defaults({ scroller })
},
})
const sheet = getProject('My project').sheet('Main', {
sequenceMode: 'page',
gsap: true,
scrollDriver: driver, // e.g. createLenisScrollDriver(lenis) — see manual
})Playground: /shared/gsap-page-mode/ (native vertical scroll), /shared/gsap-page-mode-horizontal/ (native horizontal scroll), /shared/gsap-page-mode-lenis/ (Lenis + ScrollTrigger proxy + Backstage).
registerGsapAnimation
registerGsapAnimation(animation, sheet, {
label: 'UI / Panel show',
id: 'optional-stable-id',
defaultDuration: 0.5,
onRebuildTimeline: () => rebuiltTimeline,
})| Option | Purpose |
|---|---|
label | Shown after the namespace; / segments nest in the outline and sequence tree. Optional — when omitted, Backstage uses the GSAP tween/timeline vars.id if set. |
id | Stable clip id on the sheet object. Defaults to the sanitised object key (GSAP / …). Re-registering with the same id updates the registry entry. |
defaultDuration | Clip length when first added to the sequence (defaults to tween duration in time mode; use percent in page mode). |
onRebuildTimeline | Rebuild callback when native child timing edits cannot be applied in place (timelines with editable child spans). |
Animations are paused on registration so Backstage can set progress during sequence scrubbing and playback.
ScrollTrigger (page mode, read-only sequencer)
Requires sequenceMode: 'page', gsap.registerPlugin(ScrollTrigger), and a ScrollTrigger whose scroller and axis match configureBackstageGsap({ pageScroll: { scroller, axis } }) (default: document, vertical). Set horizontal: true on triggers (or pageScroll: { axis: 'horizontal' }, which applies ScrollTrigger.defaults({ horizontal: true })). Backstage maps each trigger’s resolved start / end scroll pixels along that axis to 0–100% on the sequence. Bars are read-only in Studio; GSAP still drives scrubbing on scroll.
import gsap from 'gsap'
import {ScrollTrigger} from 'gsap/ScrollTrigger'
import {
registerGsapScrollTrigger,
registerAllGsapScrollTriggers,
} from '@unseenco/backstage/gsap'
gsap.registerPlugin(ScrollTrigger)
const sheet = project.sheet('Main', {sequenceMode: 'page', gsap: true})
// ScrollTrigger.create({ animation })
const tween = gsap.to('.box', {x: 200, duration: 1, paused: true})
const st = ScrollTrigger.create({
trigger: '.section',
start: 'top center',
end: 'bottom center',
scrub: true,
animation: tween,
})
registerGsapScrollTrigger(st, sheet, {label: 'Box scrub'})
// timeline vars.scrollTrigger
gsap.timeline({
scrollTrigger: {trigger: '.section', start: 'top top', end: '+=500', scrub: true},
paused: true,
}).to('.box', {y: 100})
registerAllGsapScrollTriggers(sheet) // optional catch-all for getAll()Outline proxies appear under GSAP / ScrollTriggers / …. They automatically show as read-only bars in the page-mode sequencer (no Add to sequence at playhead). Controlled tweens/timelines show as child rows under each trigger bar.
Page mode behaviour and scroller setup: Sheet sequence modes.
configureBackstageGsap
configureBackstageGsap({
namespace: 'GSAP',
outlineNamespace: {defaultCollapsed: true},
pageScroll: {
scroller: null, // native document (default)
axis: 'vertical', // or 'horizontal' — sets ScrollTrigger.defaults scroller + horizontal
},
suppressGsapTickerRafWarning: false,
})Returns { reset } to restore the previous config. Namespace and outline collapse settings apply when the first GSAP object is registered on a sheet.
Studio workflow
After registration, each animation appears as a proxy sheet object (no sequenced props). Built-in Studio UI provides:
- Outline — nested
GSAPnamespace; context menu Add to sequence at playhead / Remove from sequence for clip proxies (not ScrollTrigger proxies in page mode). - Detail panel — same add/remove action when a GSAP object is selected. Selecting a registered tween, timeline, or ScrollTrigger proxy also shows read-only GSAP data: target pills (Element / Object), hover highlight on page elements (Element pills), and vars from the live GSAP instance (timeline children are grouped under the parent).
- Sequence editor — GsapClipTrack rows aligned to the parent clip; GSAP timelines expose child rows mapped from
getChildren()with drag/resize and Reset to original state when timing diverges from baseline.
Only one clip per registered animation on the sequence at a time (toggle add/remove rather than stacking duplicates).
Runtime reads clip layout via sheet.sequence.__experimental_getGsapClips() (see backstage-core API).
Production bundles
Ship @unseenco/backstage/gsap and @unseenco/backstage in production when you need the bridge at runtime. Omit @unseenco/backstage/studio from production builds. GSAP remains a normal dependency.
Playground
yarn playground → /shared/gsap-time-mode/ — panel show/hide tweens, box motion, nested UI / … labels, and a rebuildable timeline choreo.
/shared/gsap-page-mode/ — native vertical document scroll. /shared/gsap-page-mode-horizontal/ — native horizontal scroll + horizontal ScrollTrigger. /shared/gsap-page-mode-lenis/ — Lenis smooth scroll + custom ScrollDriver.