Skill 18 · Hyperframes Animation
Subchapter 18.93
rules/reactive-displacement.mdMarkdown6 KBView on GitHub
Exit animation of element A is mathematically DERIVED from the entry spring of element B — a causal link: “A moves because B hit it.” Distinct from scale-swap-transition.md (which overlaps but isn’t causal) and card-morph-anchor.md (one container morphing).
A single 0→1 driver tween (the “entry spring”) feeds three concurrent derived motions in one onUpdate:
VICTIM_FRACTION (~0.4–0.5) of the driver — NOT 1.0.The victim finishing BEFORE the intruder’s entry creates the “hit then settle” rhythm; sharing one eased driver makes the impact moment mathematically synchronized.
// Both cards absolutely centered; overflow: hidden on the scene (off-stage travel);
// will-change: transform, opacity on both; intruder z-index ABOVE victim.
const INTRUDER_START_X = STAGE_W; // off-stage right
const VICTIM_END_X = -STAGE_W; // off-stage left — SAME axis, opposite direction
gsap.set("#victim", { x: 0, opacity: 1, rotation: 0 });
gsap.set("#intruder", { x: INTRUDER_START_X, opacity: 0, rotation: -INTRUDER_TILT });
const driver = { p: 0 };
tl.to(
driver,
{
p: 1,
duration: DRIVER_DUR,
ease: `back.out(${BOUNCE_FACTOR})`, // the intruder spring
onUpdate: () => {
// Intruder: full 0→1 progress maps enter (off-stage → center)
const intruderX = INTRUDER_START_X * (1 - driver.p);
const intruderOpacity = Math.min(1, driver.p * FADE_IN_SHARPNESS);
const intruderRot = -INTRUDER_TILT * (1 - driver.p); // settles to 0°
const intruder = document.getElementById("intruder");
intruder.style.transform = `translate(-50%, -50%) translateX(${intruderX}px) rotate(${intruderRot}deg)`;
intruder.style.opacity = String(intruderOpacity);
// Victim: completes its exit at VICTIM_FRACTION of the driver — by the
// time the intruder centers, the victim is already off-stage.
const victimP = Math.min(1, driver.p / VICTIM_FRACTION);
const victimX = VICTIM_END_X * victimP;
const victim = document.getElementById("victim");
victim.style.transform = `translate(-50%, -50%) translateX(${victimX}px)`;
victim.style.opacity = String(1 - victimP);
},
},
DRIVER_AT,
);
// Climax dwell — intruder holds centered for ≥ DWELL_MIN before the scene ends.const victimRot = victimP * -VICTIM_KICK_DEG; appended to its transform. VICTIM_KICK_DEG 15–25°, magnitude matched to the perceived intruder weight.±WOBBLE_AMP_DEG rotation, linearly decaying over WOBBLE_DUR via a second ease: "none" driver at DRIVER_AT + DRIVER_DUR) before stillness — “impact aftermath.”victimP on a slightly offset driver phase (cascade ripple).| token | range | notes |
|---|---|---|
| DRIVER_AT | phase-dependent | after the prior reading beat resolves; must leave ≥ DWELL_MIN of climax dwell before the scene ends |
| DRIVER_DUR | 0.6–1.4 s | short = zippy punch, long = heavy landed impact; higher bounce on long durations reads as floaty |
| BOUNCE_FACTOR | 1.2–2.0 (typ. 1.4–1.6) | stay in the back.out family (or elastic.out for oscillation) — changing family rewrites the feel |
| VICTIM_FRACTION | 0.4–0.5 | <0.4 the victim disappears before the impact reads; >0.5 feels parallel, not causal; hard cap ~0.6 |
| STAGE_W | ≥ composition width | smaller leaves the off-stage element partially visible at start |
| INTRUDER_TILT | 5–15° (typ. ~10°) | low = clean glide, high = “spin-and-plant”; sign consistent with entry direction (momentum transfer) |
| FADE_IN_SHARPNESS | 3–8 | intruder reaches opacity 1 at 1/FADE_IN_SHARPNESS of progress; must be > 1 or it’s transparent at center |
| DWELL_MIN | ≥ 1.0 s (typ. 1.0–1.5) | post-impact dwell is where the new content gets read — do not skip |
onUpdate, never separate tl.to() calls per element; independent tweens destroy the causal link (they’d merely be near each other in time).overflow: hidden on the scene — off-stage motion exceeds the frame.control-target-sync (the live-editing mirror — repeated coupled edits, nothing exits) · hacker-flip-3d (intruder text reveal during entry) · sine-wave-loop (idle breathing during the dwell) · vertical-spring-ticker (a ticker that “shoves” the previous content out).