1.1.3 — The “Animation Triad”: Timing, Motion, Meaning 🎛️ Professional-looking animation isn’t primarily about “cool effects”—it’s about making the user feel that the interface is responsive, intentional, and clear. This lesson gives you a repeatable decision framework you can apply to any GSAP animation in WordPress/Bricks. https://codepen.io/editor/ZcarecroW/pen/019da77a-6995-7501-9af8-4866e69b7f1c 1) Goal ✅ By the end of this page you will be able to: Diagnose why an animation feels “off” using Timing / Motion / Meaning. Design an animation intentionally (instead of guessing eases/durations). Implement a tiny Bricks sandbox demo that lets you feel the triad by toggling settings. Build the habit of writing a 1–2 line “meaning statement” before animating. 2) The Triad (The Mental Model) 🧠 2.1 Timing = When and how fast things happen ⏱️ Timing includes: Duration: how long a tween runs. Delay: how long before it begins. Stagger: spacing between repeated items (lists, cards). Rhythm: consistency across the site (so the UI feels coherent). What timing communicates: Fast timing → responsiveness, precision, confidence (common in UI). Slower timing → weight, calm, luxury (common in editorial / hero). Inconsistent timing → “cheap” feeling because the site has no rhythm. Practical rules of thumb (web UI): Micro feedback (hover/press/focus): Usually 0.08–0.20s Should feel instant, not “animated” Component transitions (menu open, accordion, modal): Usually 0.25–0.60s Decorative/hero sequences: Often 0.6–1.2s, but keep it purposeful If you’re unsure: start at 0.35s for UI transitions and adjust. 2.2 Motion = What path/shape the movement takes 🧭 Motion includes: Properties you animate: Prefer x/y, scale, rotate, opacity/autoAlpha Avoid animating top/left/width/height unless you know why (layout + jank risk) Easing (the velocity curve) Distance (how far an element moves) Direction (does it match the visual hierarchy and reading direction?) What motion communicates: Ease-out-ish motion → natural settling (common for UI entering) Overshoot/bounce → playful or “toy-like” (use deliberately) Too much distance → feels like the element is “traveling” instead of “appearing” A simple motion quality checklist: Does it feel like it has mass? (even a tiny bit) Does it start/stop in a way that matches the product tone? Does motion support the layout (not fight it)? 2.3 Meaning = Why the animation exists ✨ Meaning is the most ignored—and the most “pro”—part. Meaning includes: What the user learns from the animation: “This opened.” “This is clickable.” “This is the next step.” What it prioritizes: Which element is “primary” in the moment? What it prevents: Confusion Misclicks Losing context during state changes Meaning statement (habit): before animating, write: Trigger: what causes it? (load / click / scroll) User benefit: what clarity/feedback do they get? Constraint: what must not happen? (motion sickness, layout shift, delay) Example: “When the modal opens, the backdrop fades in quickly to signal a new layer, then the dialog rises slightly to confirm focus moved to it—without shifting layout.” If you can’t write the meaning statement, you’re probably adding motion “because it looks cool” (which is fine in a Dribbble shot, risky in production). 3) How the Triad Solves Real Problems (Common “It Feels Off” Diagnoses) 🧰 3.1 The animation feels laggy 😬 Usually a timing issue (too slow, too much delay), or a meaning issue (feedback arrives too late). Fixes: Reduce delay (often to 0). Shorten duration by 20–40%. Add a tiny instant cue: e.g. quick autoAlpha change first, then movement 3.2 The animation feels floaty / cheap 🎈 Usually a motion issue (ease doesn’t match UI), sometimes too much distance. Fixes: Use a more “UI” ease: power2.out, power3.out, expo.out (careful—expo can be dramatic) Reduce travel distance: y: 24 instead of y: 80 3.3 The animation feels confusing 🤔 Almost always a meaning issue. Fixes: Make the state change readable: Backdrop + dialog separation for overlays Use direction to explain hierarchy: “New layer” often comes forward/up slightly Don’t animate everything: Animate the one thing that clarifies what changed 3.4 The animation is “fine” but the site feels inconsistent 🧩 A timing rhythm problem (durations/eases vary randomly). Fixes: Define site motion tokens (you’ll formalize later in Chapter 14): 2–3 durations and 2–3 eases you reuse everywhere 4) Bricks Implementation (Sandbox Demo) 🧱 You’ll build a small section with: A headline + paragraph + button. A “Play” button that runs a reveal animation. A “Mode toggle” that switches between: UI mode (snappy, restrained) Hero mode (slower, more cinematic) 4.1 Bricks structure (what to create) Create a Section (or Container) and add: A wrapper container with class: triad-demo Inside it: Heading (H2) with class: triad-title Text (p) with class: triad-text Button row container with class: triad-controls Button 1: “Play” with class: triad-play Button 2: “Toggle mode” with class: triad-toggle (Optional) small label span with class: triad-mode Why these classes? They’re stable, readable, and easy to scope—perfect for WordPress where DOM can change. 4.2 Minimal CSS (Bricks → Page Settings → Custom CSS) .triad-demo { max-width: 720px; margin: 0 auto; padding: 48px 24px; } .triad-controls { display: flex; gap: 12px; align-items: center; margin-top: 16px; } /* Optional: make the “mode” label subtle */ .triad-mode { margin-left: 8px; font-size: 14px; opacity: 0.75; } 4.3 JavaScript (Bricks → Page Settings → Custom Code → Footer Scripts) Put it in Footer so the DOM exists when this runs (we’ll formalize DOM-ready patterns in 2.1.1) ✅ (() => { // Scope everything to each .triad-demo instance (so duplicates on the page won’t collide). const demos = document.querySelectorAll(".triad-demo"); if (!demos.length) return; demos.forEach((root) => { const title = root.querySelector(".triad-title"); const text = root.querySelector(".triad-text"); const playBtn = root.querySelector(".triad-play"); const toggleBtn = root.querySelector(".triad-toggle"); const modeLabel = root.querySelector(".triad-mode"); if (!title || !text || !playBtn || !toggleBtn) return; // Two presets to FEEL the difference between "UI motion" and "Hero motion". const presets = { ui: { label: "UI mode", timing: { duration: 0.38, stagger: 0.06 }, motion: { y: 18, ease: "power2.out" }, meaning: "Fast clarity: content appears quickly with minimal travel." }, hero: { label: "Hero mode", timing: { duration: 0.9, stagger: 0.12 }, motion: { y: 42, ease: "power3.out" }, meaning: "Cinematic emphasis: slower, longer travel, more presence." } }; let mode = "ui"; const updateModeUI = () => { if (modeLabel) { modeLabel.textContent = `${presets[mode].label} — ${presets[mode].meaning}`; } toggleBtn.setAttribute("aria-pressed", mode === "hero" ? "true" : "false"); }; // Build a function that returns a timeline, so it’s easy to reuse. const buildRevealTl = () => { const p = presets[mode]; // Clear any inline transforms/opacity from previous runs (keeps repeat plays consistent). gsap.set([title, text, playBtn], { clearProps: "all" }); // Set initial state (meaning: they are "not yet here") gsap.set([title, text, playBtn], { autoAlpha: 0, y: p.motion.y }); const tl = gsap.timeline(); tl.to([title, text, playBtn], { autoAlpha: 1, y: 0, duration: p.timing.duration, ease: p.motion.ease, stagger: p.timing.stagger }); return tl; }; let activeTl = null; playBtn.addEventListener("click", () => { // Prevent double-firing overlapping timelines if (activeTl) activeTl.kill(); activeTl = buildRevealTl(); }); toggleBtn.addEventListener("click", () => { mode = mode === "ui" ? "hero" : "ui"; updateModeUI(); }); // Initial label updateModeUI(); }); })(); 5) Micro-Exercises (Prove You Understand the Triad) 🧪 Do these in order—each targets one part of the triad. Timing-only experiment (no motion changes): Keep y and ease the same Change duration from 0.38 → 0.22 Observe: does it feel more “responsive” or more “harsh”? Motion-only experiment (same duration): Keep duration constant Change ease: power2.out → power1.out (more linear feel) power2.out → expo.out (more dramatic snap) Describe in one sentence what changed emotionally. Meaning experiment (change what’s animated): Animate only the title and leave text/button static Ask: is the state change still clear? What did you lose? Rhythm experiment (site consistency): Pick one duration + ease you like for UI: e.g. duration: 0.35, ease: power2.out Write it down somewhere as your first “motion token”. 6) Troubleshooting (Common Failure Modes) 🔧 Nothing happens when clicking “Play” Confirm GSAP is actually loaded on the page: In DevTools Console, run: typeof gsap It should return "function" or "object" (not "undefined"). Confirm your class names match exactly: .triad-demo, .triad-title, .triad-text, .triad-play, .triad-toggle Only the first demo works (if you duplicated the section) This lesson’s code scopes per .triad-demo instance—so it should work. If it doesn’t, you likely reused IDs or targeted global selectors elsewhere. Elements jump / flicker Ensure the initial state is set before animating: We use gsap.set(...autoAlpha: 0, y: ...) for that purpose. If you have CSS transitions on these elements, remove them (CSS transitions can fight GSAP). The mode label doesn’t show You didn’t add the optional .triad-mode element—either add it, or ignore it (the demo still works). 7) Verification Checklist (Do This Every Lesson) ✅ Functional Click Play: does it animate? Click Toggle mode then Play: does it feel noticeably different? Hard refresh Reload with cache bypass (your browser’s hard reload) and retest. Two-instance test Duplicate the .triad-demo section in Bricks Confirm both work independently Basic accessibility sanity Toggle button has aria-pressed updating (it does) Nothing becomes invisible-but-clickable (we use autoAlpha, which toggles visibility) 8) Your Output (What to Save) 💾 A short note in your project (or a text file) answering: Which preset felt better for UI? What duration/ease did you choose as your first “token”? Save this snippet as: pages/01-01-03-animation-triad.js Add a header comment at the top of the file like: What it does Where it’s used (Bricks sandbox page) Dependencies: GSAP core