Files
code-crispies/lessons/06-transitions-animations.json

162 lines
21 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
"title": "CSS Animations",
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
"difficulty": "intermediate",
"lessons": [
{
"id": "transitions-1",
"title": "Transitions",
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
"previewHTML": "<button class=\"btn\">Hover Me</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
"codePrefix": "/* Add transition */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"solution": " transition: background-color 0.3s;",
"previewContainer": "preview-area",
"concept": {
"explanation": "CSS transitions interpolate (calculate in-between values) smoothly between a property's start and end values over a specified duration. When you hover the button, the browser detects the background-color change from black to white and automatically generates intermediate color values at each frame (typically 60 frames per second). For colors, the browser converts both values to RGB, then calculates the proportional change for each channel (red, green, blue) at each timestamp. For example, at 50% through a 0.3s transition, the color would be halfway between black (rgb(0,0,0)) and white (rgb(255,255,255)), resulting in gray (rgb(128,128,128)). This frame-by-frame interpolation creates the smooth visual effect you see.",
"diagram": "How CSS Transitions Interpolate Values\n\nTransition: background-color 0.3s\nStart: black → End: white\n\nTime progression (60fps = 60 frames in 0.3s):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n0.00s (0%) rgb(0, 0, 0) ███████ black\n ↓ interpolate\n0.05s (17%) rgb(43, 43, 43) ███████ dark gray\n ↓ interpolate\n0.15s (50%) rgb(128,128,128) ███████ gray\n ↓ interpolate\n0.25s (83%) rgb(212,212,212) ███████ light gray\n ↓ interpolate\n0.30s (100%) rgb(255,255,255) ███████ white\n\nRGB interpolation formula at time t:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nvalue = start + (end - start) × progress\n\nAt t=0.15s (50% through 0.3s duration):\nprogress = 0.15 / 0.3 = 0.5\n\nRed: 0 + (255 - 0) × 0.5 = 128\nGreen: 0 + (255 - 0) × 0.5 = 128\nBlue: 0 + (255 - 0) × 0.5 = 128\n\nResult: rgb(128, 128, 128) ✓\n\nBrowser rendering process:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1. Detect property change (hover triggers)\n2. Start transition timer (0.3s duration)\n3. Calculate frame count (0.3s × 60fps = 18 frames)\n4. For each frame:\n - Calculate progress (elapsed / duration)\n - Interpolate RGB values\n - Repaint element\n5. End at final value\n\nTransitionable properties:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nColors: rgb values interpolate\nLengths: px, rem, % interpolate \nTransforms: matrix values interpolate\nOpacity: 0-1 range interpolates\nNOT text: \"foo\" → \"bar\" can't interpolate!"
},
"validations": [
{
"type": "contains",
"value": "transition",
"message": "Use the <kbd>transition</kbd> property",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "transition:\\s*background-color\\s*0\\.3s",
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "transitions-2",
"title": "Timing Funcs",
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
"previewHTML": "<button class=\"btn\">Timing</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
"codePrefix": "/* Set timing function */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"solution": " transition-timing-function: ease-in-out;",
"previewContainer": "preview-area",
"concept": {
"explanation": "Timing functions (also called easing functions) control the rate of change during a transition or animation by applying a mathematical curve to the linear progress over time. Instead of changing at a constant speed (linear), timing functions accelerate or decelerate at different points, making animations feel more natural and realistic. The browser uses Bézier curves (cubic-bezier) to calculate the output progress for each input time value. For example, ease-in-out starts slow (low acceleration), speeds up in the middle (high velocity), then slows down at the end (deceleration), mimicking real-world physics where objects don't instantly reach full speed or stop abruptly.",
"diagram": "Timing Functions & Animation Pacing\n\nLinear progress over time:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 25% 50% 75% 100%\nSpeed: ═══════════════════════════ constant\n\nEase-in (accelerate):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 6% 25% 56% 100%\nSpeed: ─────────────────────────▶ speeds up\n slow fast\n\nEase-out (decelerate):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 44% 75% 94% 100%\nSpeed: ◀───────────────────────── slows down\n fast slow\n\nEase-in-out (accelerate then decelerate):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 10% 50% 90% 100%\nSpeed: ─────▶━━━━━━◀───── natural motion\n slow fast slow\n\nBézier curve visualization:\n\n 1 ┤ ╭──── ease-out\n │ ╭───╯ (fast start)\n │ ╭────╯\n0.5 ┤ ╭────╯──── ease-in-out\n │ ╭───╯ (smooth)\n │ ╭───╯\n 0 ┤──╯─────────────────── linear\n └──┬────┬────┬────┬──── ease-in\n 0 0.25 0.5 0.75 1 (slow start)\n Time →\n\nCommon timing functions:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nlinear cubic-bezier(0, 0, 1, 1)\nease cubic-bezier(0.25, 0.1, 0.25, 1) [default]\nease-in cubic-bezier(0.42, 0, 1, 1)\nease-out cubic-bezier(0, 0, 0.58, 1)\nease-in-out cubic-bezier(0.42, 0, 0.58, 1)\n\nReal-world analogy:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCar accelerating from stop sign:\n ease-in → Pressing gas pedal gradually\n\nCar approaching red light:\n ease-out → Braking smoothly to stop\n\nCar between two stop signs:\n ease-in-out → Accelerate, cruise, brake"
},
"validations": [
{
"type": "contains",
"value": "transition-timing-function",
"message": "Use <kbd>transition-timing-function</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Set timing to <kbd>ease-in-out</kbd>"
}
]
},
{
"id": "transitions-3",
"title": "Keyframes",
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
"previewHTML": "<div class=\"ball\"></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
"codePrefix": "/* Define keyframes and apply animation */\n@keyframes bounce {",
"initialCode": "",
"codeSuffix": "}\n.ball { }",
"solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
"previewContainer": "preview-area",
"concept": {
"explanation": "Keyframe animations define multiple snapshots (keyframes) of property values at specific points in time, and the browser interpolates smoothly between them. Unlike transitions which only animate from one state to another, keyframes let you define complex multi-step animations with precise control over intermediate states. Each keyframe is marked with a percentage (0% is the start, 100% is the end, 50% is halfway), and you can define as many keyframes as needed. The browser calculates the timing between keyframes: in a 1s animation with keyframes at 0%, 50%, and 100%, the animation spends 0.5s transitioning from 0% to 50%, then another 0.5s from 50% to 100%. The 'infinite' keyword makes the animation loop continuously, restarting from 0% each time it completes.",
"diagram": "Keyframe Animation Timeline & Interpolation\n\n@keyframes bounce {\n 0% { transform: translateY(0px); } ← implicit start\n 50% { transform: translateY(-20px); } ← explicit midpoint\n 100% { transform: translateY(0px); } ← implicit end\n}\n\nanimation: bounce 1s infinite;\n\nTimeline breakdown (1 second duration):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n0.0s (0%) ●─────────────────── translateY(0px)\n │ interpolate [starting position]\n │ over 0.5s\n ↓\n0.5s (50%) ●─────────────────── translateY(-20px)\n │ interpolate [peak - 20px up]\n │ over 0.5s\n ↓\n1.0s (100%) ●─────────────────── translateY(0px)\n ↓ infinite loop [back to start]\n0.0s restart ●\n\nVisual representation:\n\n -20px ↑ ● ← 50% keyframe (peak)\n │ ╲\n │╱ ╲\n 0px ●─────● ← 0% and 100% keyframes\n ↑ ↑\n 0s 1s\n\nInterpolation between keyframes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nFrom 0% to 50% (0s to 0.5s):\nStart: translateY(0px)\nEnd: translateY(-20px)\n\nAt 0.25s (halfway between 0% and 50%):\nprogress = 0.25 / 0.5 = 0.5\nvalue = 0 + (-20 - 0) × 0.5 = -10px\nResult: translateY(-10px) ✓\n\nFrom 50% to 100% (0.5s to 1s):\nStart: translateY(-20px)\nEnd: translateY(0px)\n\nAt 0.75s (halfway between 50% and 100%):\nprogress = (0.75 - 0.5) / 0.5 = 0.5\nvalue = -20 + (0 - (-20)) × 0.5 = -10px\nResult: translateY(-10px) ✓\n\nKeyframes vs Transitions:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTransitions:\n A → B (one state change)\n Triggered by hover/focus/class change\n Example: button:hover { color: red; }\n\nKeyframes:\n A → B → C → D... (multiple states)\n Runs automatically when element exists\n Example: loading spinner, bounce effect\n Can loop infinitely\n\nImplicit keyframes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nIf you don't define 0% or 100%, browser uses\ncurrent computed values:\n\n@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n↓ Browser expands to:\n@keyframes bounce {\n 0% { transform: translateY(0px); } ← added\n 50% { transform: translateY(-20px); }\n 100% { transform: translateY(0px); } ← added\n}"
},
"validations": [
{
"type": "contains",
"value": "@keyframes bounce",
"message": "Define <kbd>@keyframes bounce</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "50%.*transform: translateY\\(-20px\\)",
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation",
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "transitions-4",
"title": "Animation Properties",
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
"previewHTML": "<div class=\"box\">Pulse</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: black; color: white; display: flex; align-items: center; justify-content: center; margin: 2rem auto; } @keyframes pulse { 0% { background: black; color: white; transform: scale(1); } 50% { background: white; color: black; transform: scale(1.2); } 100% { background: limegreen; color: black; transform: scale(1); } }",
"sandboxCSS": "",
"codePrefix": "/* Apply animation properties */\n.box {",
"initialCode": "",
"codeSuffix": "}",
"solution": " animation-name: pulse;\n animation-duration: 2s;\n animation-delay: 1s;\n animation-iteration-count: 2;\n animation-fill-mode: forwards;",
"previewContainer": "preview-area",
"concept": {
"explanation": "Animation properties give you precise control over playback timing and behavior. Animation-delay postpones the start of the animation by a specified time, useful for choreographing multiple animations or waiting for user attention. Animation-iteration-count determines how many times the animation repeats (1 for once, 2 for twice, 'infinite' for forever). Animation-fill-mode controls what styles apply before and after the animation: 'none' (default) removes animation styles when not playing, 'forwards' keeps the final keyframe (100%) styles after completion, 'backwards' applies the first keyframe (0%) styles during the delay period, and 'both' combines forwards and backwards. These properties give you fine-grained control over exactly when animations start, how long they run, and what happens before/after playback.",
"diagram": "Animation Properties & Timeline Control\n\nanimation-name: pulse;\nanimation-duration: 2s;\nanimation-delay: 1s;\nanimation-iteration-count: 2;\nanimation-fill-mode: forwards;\n\nComplete timeline:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n0s 1s 3s 5s\n│───────────│───────────│───────────│\n│ DELAY │ ITERATION │ ITERATION │ END\n│ (wait) │ #1 │ #2 │ (hold)\n│ │ │ │\n│ ●●●●●●● │ ▶────────▶│ ▶────────▶│ ████\n│ waiting │ playing │ playing │ frozen\n│ │ (2s) │ (2s) │ at\n│ │ │ │ 100%\n\nElement state at each phase:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBefore delay (0s - 1s):\n background: black ← original CSS\n (animation hasn't started yet)\n\nDuring iteration 1 (1s - 3s):\n 0%: background: black\n 50%: background: white\n 100%: background: limegreen\n (animating through keyframes)\n\nDuring iteration 2 (3s - 5s):\n Repeats: black → white → limegreen\n (second playthrough)\n\nAfter animation (5s+):\n background: limegreen ← fill-mode: forwards\n (stuck at 100% keyframe)\n\nanimation-fill-mode explained:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nnone (default):\n Before: original CSS ●●●●●●●\n During: animation ▶────▶\n After: original CSS ●●●●●●●\n\nforwards:\n Before: original CSS ●●●●●●●\n During: animation ▶────▶\n After: 100% keyframe ████████ ← stays!\n\nbackwards:\n Before: 0% keyframe ████████ ← applies!\n During: animation ▶────▶\n After: original CSS ●●●●●●●\n\nboth:\n Before: 0% keyframe ████████ ← applies!\n During: animation ▶────▶\n After: 100% keyframe ████████ ← stays!\n\nanimation-iteration-count:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1 ▶────▶ (play once)\n2 ▶────▶ ▶────▶ (play twice)\n3 ▶────▶ ▶────▶ ▶────▶\ninfinite ▶────▶ ▶────▶ ▶────▶... (loop forever)\n2.5 ▶────▶ ▶────▶ ▶── (2.5 times)\n\nanimation-delay use cases:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nStaggered animations (cascade effect):\n .item:nth-child(1) { animation-delay: 0s; }\n .item:nth-child(2) { animation-delay: 0.1s; }\n .item:nth-child(3) { animation-delay: 0.2s; }\n\n Result: items animate one after another ↓\n\nNegative delay (start mid-animation):\n animation-delay: -1s; ← starts 1s into animation\n Useful for randomizing loop positions\n\nShorthand syntax:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nanimation: pulse 2s 1s 2 forwards;\n │ │ │ │ └─ fill-mode\n │ │ │ └──── iteration-count\n │ │ └─────── delay\n │ └────────── duration\n └──────────────── name"
},
"validations": [
{
"type": "property_value",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Set <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-duration", "expected": "2s" },
"message": "Set <kbd>animation-duration: 2s</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-delay", "expected": "1s" },
"message": "Set <kbd>animation-delay: 1s</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-iteration-count", "expected": "2" },
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
}
]
}
]
}