Files
hyperframes/packages/shader-transitions
Miguel Ángel ae717331ce fix(studio): soft-reload GSAP property edits, preserve shader cache (#1129)
* fix(studio): soft-reload GSAP property edits without iframe reload

GSAP property value edits (opacity, x, scale, etc.) now update the live
timeline inside the preview iframe without triggering a full iframe
reload. This preserves the WebGL context and shader transition cache,
eliminating the loading overlay that appeared on every property edit.

Implementation:
- New gsapSoftReload.ts: kills the old GSAP timeline, re-executes the
  updated script, calls __hfForceTimelineRebind(), and re-seeks to the
  current time. Falls back to full reload on failure.
- useGsapScriptCommits: passes softReload: true for property value edits
  via the existing (previously unused) softReload flag on commitMutation.
- hyper-shader.ts: exposes __hfSuppressSceneMutations on the window so
  the soft-reload can suppress the MutationObserver during re-execution.
- hyper-shader.ts: getDocumentScriptSignature now excludes pure GSAP
  animation scripts from the cache key hash, so full reloads (undo,
  external changes) don't invalidate transition caches when only
  animation values changed.

* fix(studio): wrap soft-reload script in IIFE to avoid const redeclaration

The new script ran in the same global scope as the old one, causing
Identifier tl has already been declared errors from const/let
re-declarations. Wrapping in an IIFE creates a new lexical scope.
Also remove the old script element before inserting the new one.

* fix(studio): return scriptText from mutation API, drop client-side HTML parsing

The mutation API already has the extracted GSAP script text (newScript)
after rewriting. Return it as scriptText in the response so
applySoftReload receives the script directly instead of parsing HTML
client-side. This avoids DOMParser compatibility issues across test
environments and is more reliable than regex-based extraction.

* fix(studio): align soft-reload script heuristic with server-side parser

The client's findGsapScriptElement only matched gsap.timeline and
__timelines. The server's extractGsapScriptBlock also matches .to( and
.set(. Aligned the client heuristic to prevent silent fallback to full
reload for compositions that use tl.to() without gsap.timeline in the
same script.

* fix(studio): address hf#1129 review — multi-script guard, scope docs

- Return false (fallback to full reload) when multiple GSAP scripts
  exist in the document, since it's ambiguous which one to replace
- Add docstring scoping the optimization to root-document scripts
  (template-wrapped sub-compositions fall back to full reload)
- Add code comment explaining the IIFE scope constraint
- Add test for the multi-script guard

* fix(studio): align cache key filter with soft-reload script heuristic

isGsapAnimationOnlyScript now also matches .to( and .set( patterns,
matching findGsapScriptElement. Scripts using only tl.to() without
gsap.timeline were excluded from soft-reload but still busted the
shader cache on full-reload paths (undo, external changes).
2026-05-29 23:34:57 -04:00
..
2026-05-30 02:22:16 +00:00

@hyperframes/shader-transitions

WebGL shader transitions for HyperFrames compositions. Renders GPU-accelerated scene-to-scene transitions using fragment shaders, driven by GSAP timelines.

Install

npm install @hyperframes/shader-transitions

Or load directly via CDN:

<script src="https://cdn.jsdelivr.net/npm/@hyperframes/shader-transitions/dist/index.global.js"></script>

Usage

import { init } from "@hyperframes/shader-transitions";

const tl = init({
  bgColor: "#0a0a0a",
  accentColor: "#ff6b2b",
  scenes: ["scene-1", "scene-2", "scene-3"],
  transitions: [
    { time: 3, shader: "domain-warp", duration: 0.8 },
    { time: 8, shader: "light-leak", duration: 0.7 },
  ],
});

The init() function pre-captures animated scene samples for every transition, composites cached samples with the selected shader during playback, and returns a GSAP timeline. Scene animations keep advancing through shader transitions without running DOM captures in the playback loop. If WebGL is unavailable, it falls back to normal timeline playback without shader compositing.

When the browser exposes Chrome's experimental CanvasDrawElement API, scene capture uses native HTML-in-canvas via drawElementImage(). Other browsers keep using the existing html2canvas fallback. You can feature-detect the native path with isHtmlInCanvasCaptureSupported().

With an existing timeline

Pass your own GSAP timeline to layer transitions onto it:

const tl = gsap.timeline({ paused: true });
// ... add your scene animations ...

init({
  bgColor: "#000",
  scenes: ["intro", "demo", "outro"],
  transitions: [
    { time: 5, shader: "cinematic-zoom" },
    { time: 12, shader: "glitch", duration: 0.5 },
  ],
  timeline: tl,
});

Available shaders

Shader Description
domain-warp Organic noise-based warp with glowing edge
ridged-burn Ridged noise burn with sparks and heat glow
whip-pan Horizontal motion blur simulating a fast camera pan
sdf-iris Circular iris wipe with glowing ring edge
ripple-waves Concentric ripple distortion radiating from center
gravitational-lens Warping gravity well with chromatic aberration
cinematic-zoom Radial zoom blur with chromatic fringing
chromatic-split RGB channel separation expanding from center
glitch Digital glitch with block displacement and scanlines
swirl-vortex Spiral rotation with noise-based warping
thermal-distortion Heat shimmer rising from the bottom
flash-through-white Flash to white then reveal the next scene
cross-warp-morph Noise-driven morph blending both scenes
light-leak Warm cinematic light leak with lens flare

API

init(config): GsapTimeline

Option Type Required Description
bgColor string yes Fallback background color (hex) for scene capture. Use the composition's body/canvas background — individual scenes set their own background-color via CSS.
accentColor string no Accent color (hex) for shader glow effects
scenes string[] yes Element IDs of each scene, in order
transitions TransitionConfig[] yes Transition definitions (see below)
timeline GsapTimeline no Existing timeline to attach transitions to
compositionId string no Override the data-composition-id for timeline registration
previewCaptureFps number no Browser preview pre-capture samples per transition second. Defaults to 30; rendering uses deterministic per-frame compositing instead.

Browser preview capture scale and transition-prep loading UI ownership are controlled by <hyperframes-player> (shader-capture-scale, shader-loading) instead of composition code. Direct non-player previews keep the built-in full-fidelity loading fallback.

Browser previews store captured transition snapshots in IndexedDB using a key derived from composition ID, scene DOM/style signatures, transition timing, capture FPS, scale, and dimensions. On refresh, matching snapshots are reloaded into WebGL textures instead of being captured again. Runtime scene or stylesheet edits mark only adjacent transition caches dirty; recapture is deferred until playback so editing stays responsive.

TransitionConfig

Option Type Default Description
time number Start time in seconds
shader ShaderName Shader name from the table above
duration number 0.7 Transition duration in seconds
ease string "power2.inOut" GSAP easing function

SHADER_NAMES

Array of all available shader name strings, useful for validation or building UIs.

import { SHADER_NAMES } from "@hyperframes/shader-transitions";
// ["domain-warp", "ridged-burn", "whip-pan", ...]

Distribution

Format File Use case
ESM dist/index.js Bundlers (Vite, webpack, etc.)
CJS dist/index.cjs Node.js / require()
IIFE dist/index.global.js <script> tag, CDN (global: HyperShader)

All formats include source maps. TypeScript definitions included.

License

MIT