Skip to main content
Glama
guidance.ts26.3 kB
/** * Animation Guidance - Implementation guidance for animation properties */ import { ImplementationGuidance } from '../../types.js'; export const ANIMATION_GUIDANCE: Record<string, ImplementationGuidance> = { 'transition': { basic_usage: '.element { transition: all 0.3s ease; }', best_practices: [ 'Specify individual properties instead of "all" for better performance', 'Use reasonable durations (0.2s-0.5s for most interactions)', 'Choose appropriate easing functions', 'Consider reduced motion preferences' ], fallbacks: [ 'Provide instant state changes for older browsers', 'Use JavaScript for complex animations', 'Consider CSS feature queries' ], example_code: ` .button { transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; } .button:hover { background-color: var(--primary-color); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } /* Respect user preferences */ @media (prefers-reduced-motion: reduce) { .button { transition: none; } } /* Fallback for older browsers */ @supports not (transition: background-color 0.2s ease) { .button:hover { background-color: var(--primary-color); } }` }, 'animation': { basic_usage: '.element { animation: slideIn 0.5s ease-out; }', best_practices: [ 'Use transform and opacity for smooth animations', 'Keep animations short and purposeful', 'Provide animation controls for accessibility', 'Use will-change for performance optimization' ], fallbacks: [ 'Provide static fallbacks for older browsers', 'Use JavaScript for complex animations', 'Consider progressive enhancement' ], example_code: ` @keyframes slideIn { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .slide-in { animation: slideIn 0.5s ease-out; } /* Loading spinner */ @keyframes spin { to { transform: rotate(360deg); } } .spinner { animation: spin 1s linear infinite; will-change: transform; } /* Respect user preferences */ @media (prefers-reduced-motion: reduce) { .slide-in, .spinner { animation: none; } } /* Performance optimization */ .animated-element { will-change: transform, opacity; } .animated-element.animation-complete { will-change: auto; }` }, 'keyframes': { basic_usage: '@keyframes myAnimation { 0% { opacity: 0; } 100% { opacity: 1; } }', best_practices: [ 'Use percentage-based keyframes for flexibility', 'Animate transform and opacity for best performance', 'Keep keyframes simple and purposeful', 'Use descriptive animation names' ], fallbacks: [ 'Provide static end states', 'Use JavaScript for complex sequences', 'Consider CSS feature queries' ], example_code: ` @keyframes fadeInUp { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } } @keyframes slideInFromLeft { from { transform: translateX(-100%); } to { transform: translateX(0); } } /* Complex multi-step animation */ @keyframes complexAnimation { 0% { opacity: 0; transform: translateY(30px) scale(0.8); } 50% { opacity: 0.8; transform: translateY(-10px) scale(1.1); } 100% { opacity: 1; transform: translateY(0) scale(1); } }` }, 'animation-timing-function': { basic_usage: '.element { animation-timing-function: ease-in-out; }', best_practices: [ 'Use ease-out for entrances', 'Use ease-in for exits', 'Use ease-in-out for smooth interactions', 'Consider cubic-bezier for custom easing', 'Always include prefers-reduced-motion support' ], fallbacks: [ 'Use linear for older browsers', 'Provide simplified timing functions', 'Consider JavaScript easing libraries' ], example_code: ` .entrance { animation-timing-function: ease-out; } .exit { animation-timing-function: ease-in; } .bounce { animation-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55); } /* Custom easing functions */ .smooth { animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } .spring { animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275); } /* Accessibility: Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { .entrance, .exit, .bounce, .smooth, .spring { animation-timing-function: ease; animation-duration: 0.01ms; } }` }, '@starting-style': { basic_usage: '@starting-style { .element { opacity: 0; } }', best_practices: [ 'Use for animating display property changes', 'Combine with transition for smooth entry animations', 'Essential for height/width auto animations', 'Works with discrete property changes', 'Always include prefers-reduced-motion support' ], fallbacks: [ 'Use JavaScript for older browsers', 'Provide instant state changes as fallback', 'Consider progressive enhancement' ], example_code: ` /* Animate display property changes */ .dialog { transition: opacity 0.3s ease, overlay 0.3s ease allow-discrete, display 0.3s ease allow-discrete; } .dialog[open] { opacity: 1; } @starting-style { .dialog[open] { opacity: 0; } } /* Animate height changes */ .expandable { height: auto; transition: height 0.3s ease; interpolate-size: allow-keywords; } @starting-style { .expandable { height: 0; } } /* Accessibility: Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { .dialog { transition: none; } .expandable { transition: none; } @starting-style { .dialog[open] { opacity: 1; } .expandable { height: auto; } } } /* Fallback for older browsers */ @supports not (interpolate-size: allow-keywords) { .expandable { height: 0; overflow: hidden; } .expandable.expanded { height: auto; } }` }, 'interpolate-size': { basic_usage: '.element { interpolate-size: allow-keywords; }', best_practices: [ 'Enable smooth animations of auto, min-content, max-content', 'Combine with @starting-style for display animations', 'Use for height/width transitions with intrinsic sizing', 'Essential for modern layout animations', 'Always include prefers-reduced-motion support' ], fallbacks: [ 'Use fixed dimensions for older browsers', 'JavaScript-based height calculations', 'CSS Grid/Flexbox alternatives' ], example_code: ` /* Enable auto keyword interpolation */ .container { height: auto; transition: height 0.3s ease; interpolate-size: allow-keywords; } @starting-style { .container { height: 0; } } /* Width animations with intrinsic sizing */ .sidebar { width: max-content; transition: width 0.4s ease; interpolate-size: allow-keywords; } .sidebar.collapsed { width: 0; } /* Performance optimization with GPU-accelerated properties */ .gpu-optimized { transform: translateZ(0); /* Force GPU layer */ transition: transform 0.3s ease, opacity 0.3s ease; will-change: transform, opacity; } /* Only animate GPU-accelerated properties for best performance */ .performant-animation { /* Good: GPU-accelerated properties */ transform: scale(1) rotate(0deg) translate(0, 0); opacity: 1; filter: blur(0px) brightness(1); /* Avoid animating these for performance */ /* width, height, top, left, margin, padding */ } /* Use transform instead of position changes */ .slide-in { transform: translateX(-100%); transition: transform 0.3s ease; } .slide-in.active { transform: translateX(0); } /* Accessibility: Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { .container, .sidebar, .gpu-optimized, .slide-in { transition: none; } .slide-in.active { transform: translateX(0); } @starting-style { .container { height: auto; } } }` }, 'scroll-timeline': { basic_usage: '.element { scroll-timeline: myTimeline block; }', best_practices: [ 'Use descriptive timeline names', 'Choose appropriate scroll axis (block, inline, x, y)', 'Combine with animation-timeline for scroll-driven animations', 'Consider browser support for progressive enhancement', 'Always include prefers-reduced-motion support' ], fallbacks: [ 'Use Intersection Observer API for older browsers', 'JavaScript scroll event listeners', 'CSS-only alternatives with viewport units' ], example_code: ` /* Create scroll timeline on scroll container */ .scroll-container { scroll-timeline-name: container-scroll; scroll-timeline-axis: block; } /* Alternative shorthand syntax */ .scroll-container { scroll-timeline: container-scroll block; } /* Use timeline for scroll-driven animation */ .progress-bar { animation: progress-fill linear; animation-timeline: container-scroll; } @keyframes progress-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } } /* Multiple scroll timelines */ .horizontal-scroll { scroll-timeline: horizontal-timeline inline; } .vertical-scroll { scroll-timeline: vertical-timeline block; } /* Accessibility: Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { .progress-bar { animation: none; transform: scaleX(1); /* Show completed state */ } } /* Fallback using Intersection Observer */ @supports not (animation-timeline: scroll()) { .progress-bar { transition: transform 0.1s ease-out; } }` }, 'view-timeline': { basic_usage: '.element { view-timeline: myView block; }', best_practices: [ 'Use for animations triggered by element visibility', 'Combine with animation-range for precise control', 'Consider view-timeline-inset for offset adjustments', 'Use meaningful timeline names', 'Always include prefers-reduced-motion support' ], fallbacks: [ 'Intersection Observer API', 'Scroll event listeners with getBoundingClientRect', 'CSS animations with delays' ], example_code: ` /* Element creates view timeline as it enters viewport */ .card { view-timeline-name: card-view; view-timeline-axis: block; } /* Shorthand syntax */ .card { view-timeline: card-view block; } /* Animation triggered by view timeline */ .card-content { opacity: 0; transform: translateY(50px); animation: card-enter linear; animation-timeline: card-view; animation-range: entry 0% entry 100%; } @keyframes card-enter { to { opacity: 1; transform: translateY(0); } } /* Multiple elements with view timelines */ .reveal { view-timeline: reveal-timeline; animation: reveal-animation linear; animation-timeline: reveal-timeline; animation-range: contain 0% contain 50%; } @keyframes reveal-animation { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } } /* View timeline with inset */ .parallax-element { view-timeline: parallax-view; view-timeline-inset: 100px 50px; } /* Accessibility: Respect user motion preferences */ @media (prefers-reduced-motion: reduce) { .card-content, .reveal { animation: none; opacity: 1; transform: translateY(0) scale(1); } }` }, 'animation-timeline': { basic_usage: '.element { animation-timeline: myTimeline; }', best_practices: [ 'Connect animations to scroll or view timelines', 'Use with animation-range for precise control', 'Combine with GPU-accelerated properties', 'Provide fallbacks for unsupported browsers' ], fallbacks: [ 'JavaScript-based scroll animations', 'CSS transitions with scroll events', 'Static animations as fallback' ], example_code: ` /* Connect animation to scroll timeline */ .parallax { animation: parallax-move linear; animation-timeline: scroll(root block); } @keyframes parallax-move { to { transform: translateY(-50%); } } /* Connect to named timeline */ .hero-text { animation: hero-fade linear; animation-timeline: hero-scroll; } /* Multiple animations on different timelines */ .complex-animation { animation: fade-in linear, slide-up linear, scale-in linear; animation-timeline: view-timeline, view-timeline, scroll-timeline; animation-range: entry 0% entry 100%, entry 0% entry 100%, 0% 50%; } /* Auto timeline (nearest scroll container) */ .auto-scroll { animation: auto-animation linear; animation-timeline: scroll(); } /* Anonymous view timeline */ .view-driven { animation: view-animation linear; animation-timeline: view(); }` }, 'animation-range': { basic_usage: '.element { animation-range: entry 0% entry 100%; }', best_practices: [ 'Use named ranges (entry, exit, contain, cover) for clarity', 'Combine start and end values for precise control', 'Use percentage values for relative positioning', 'Consider element size when setting ranges' ], fallbacks: [ 'Full animation duration without ranges', 'JavaScript-based range calculations', 'CSS custom properties for range values' ], example_code: ` /* Animation during element entry */ .fade-in { animation: fade linear; animation-timeline: view(); animation-range: entry 0% entry 100%; } /* Animation during exit */ .fade-out { animation: fade-out linear; animation-timeline: view(); animation-range: exit 0% exit 100%; } /* Animation while element is contained in viewport */ .scale-while-visible { animation: scale-pulse linear; animation-timeline: view(); animation-range: contain 0% contain 100%; } /* Complex range with multiple segments */ .complex-range { animation: complex-animation linear; animation-timeline: view(); animation-range: entry 50% exit 50%; } /* Individual start and end properties */ .precise-control { animation: precise-animation linear; animation-timeline: view(); animation-range-start: entry 25%; animation-range-end: exit 75%; } /* Scroll-based ranges with pixel values */ .scroll-range { animation: scroll-animation linear; animation-timeline: scroll(); animation-range: 100px 500px; } /* Multiple animations with different ranges */ .multi-range { animation: fade-in linear, slide-up linear; animation-timeline: view(), view(); animation-range: entry 0% entry 50%, entry 50% entry 100%; }` }, 'view-timeline-inset': { basic_usage: '.element { view-timeline-inset: 100px 50px; }', best_practices: [ 'Use to adjust when view timeline starts/ends', 'Positive values shrink the timeline range', 'Negative values extend the timeline range', 'Consider responsive design with relative units' ], fallbacks: [ 'JavaScript-based offset calculations', 'CSS custom properties for dynamic values', 'Default timeline behavior' ], example_code: ` /* Symmetric inset - shrink timeline by 100px on both sides */ .card { view-timeline: card-timeline; view-timeline-inset: 100px; } /* Asymmetric inset - different start and end offsets */ .hero { view-timeline: hero-timeline; view-timeline-inset: 200px 50px; } /* Extend timeline with negative values */ .early-trigger { view-timeline: early-timeline; view-timeline-inset: -100px -50px; } /* Responsive insets with relative units */ .responsive-timeline { view-timeline: responsive-timeline; view-timeline-inset: 10vh 5vh; } /* Individual inset properties */ .individual-inset { view-timeline: individual-timeline; view-timeline-inset-block-start: 100px; view-timeline-inset-block-end: 50px; } /* Use with animation for precise timing */ .precise-timing { view-timeline: precise-timeline; view-timeline-inset: 150px 100px; animation: precise-animation linear; animation-timeline: precise-timeline; animation-range: entry 0% exit 100%; }` }, 'timeline-scope': { basic_usage: '.element { timeline-scope: myTimeline; }', best_practices: [ 'Use to make timelines accessible to descendant elements', 'Declare at common ancestor of timeline creator and consumer', 'Use meaningful timeline names', 'Consider timeline hierarchy and scope' ], fallbacks: [ 'Move timeline declaration to appropriate ancestor', 'JavaScript-based timeline management', 'Global timeline naming conventions' ], example_code: ` /* Declare timeline scope at container level */ .page-container { timeline-scope: page-scroll, hero-view; } /* Child creates the timeline */ .scroll-area { scroll-timeline: page-scroll block; } /* Descendant uses the timeline */ .animated-element { animation: slide-in linear; animation-timeline: page-scroll; } /* Multiple timeline scopes */ .section { timeline-scope: section-scroll, card-views; } /* Nested scopes */ .outer-container { timeline-scope: global-timeline; } .inner-container { timeline-scope: local-timeline; } /* Timeline scope with view timelines */ .gallery { timeline-scope: card-animations; } .gallery-item { view-timeline: card-animations; } .gallery-item .content { animation: card-reveal linear; animation-timeline: card-animations; animation-range: entry 0% entry 100%; }` }, 'view-transition-name': { basic_usage: '.element { view-transition-name: card; }', best_practices: [ 'Use unique view-transition-name values for each element', 'Root element automatically gets view-transition-name: root', 'Combine with document.startViewTransition() or element.startViewTransition() for scoped transitions (Chrome 140+)', 'Style transitions using ::view-transition pseudo-elements', 'Use @starting-style for entry animations', 'Consider accessibility - provide reduced motion alternatives', 'Works with CSS-only - no JavaScript needed for styling' ], fallbacks: [ 'Use CSS transitions/animations for older browsers', 'Implement JavaScript-based transitions', 'Provide instant state changes', 'Use @supports for feature detection' ], example_code: ` /* ✅ CSS VIEW TRANSITIONS - Smooth animated state changes */ /* 1. BASIC DOCUMENT TRANSITION - Tag elements to participate */ .card { view-transition-name: card; } .header { view-transition-name: header; } /* Root is automatically named "root" */ :root { view-transition-name: root; } /* 2. CUSTOMIZE THE TRANSITION - Style pseudo-elements */ ::view-transition-old(card), ::view-transition-new(card) { animation-duration: 0.5s; } ::view-transition-group(card) { animation-duration: 0.5s; animation-timing-function: ease-in-out; } /* 3. CUSTOM ANIMATIONS - Override default cross-fade */ @keyframes slide-up { from { transform: translateY(100%); } } @keyframes slide-down { to { transform: translateY(100%); } } ::view-transition-old(card) { animation: slide-down 0.4s ease-in; } ::view-transition-new(card) { animation: slide-up 0.4s ease-out; } /* 4. SCOPED VIEW TRANSITIONS (Chrome 140+) */ /* Apply to container with contain: layout */ .sidebar { contain: layout; /* Call element.startViewTransition() on this element */ } .sidebar .nav-item { view-transition-name: nav-item; } /* Scoped transition pseudo-elements get injected into .sidebar */ .sidebar::view-transition { /* Styles for the scoped transition root */ } .sidebar::view-transition-group(nav-item) { animation-duration: 0.3s; } /* 5. DIFFERENT TRANSITIONS FOR DIFFERENT GROUPS */ /* Fast animations for small elements */ ::view-transition-group(icon) { animation-duration: 0.2s; } /* Slower for large content areas */ ::view-transition-group(content) { animation-duration: 0.6s; } /* 6. ADVANCED: CUSTOM EASING AND TIMING */ ::view-transition-old(*), ::view-transition-new(*) { animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } /* Scale effect for card transitions */ @keyframes scale-up { from { transform: scale(0.8); } } ::view-transition-new(card) { animation: scale-up 0.4s ease-out; } /* 7. WILDCARD SELECTOR - Style all groups */ ::view-transition-group(*) { animation-duration: 0.4s; } /* 8. ISOLATE SPECIFIC GROUPS */ ::view-transition-image-pair(thumbnail) { isolation: isolate; mix-blend-mode: normal; } /* 9. DISABLE TRANSITION FOR SPECIFIC ELEMENTS */ .no-transition { view-transition-name: none; } /* 10. ENTRY ANIMATIONS with @starting-style */ @starting-style { .modal { opacity: 0; transform: scale(0.9); } } .modal { view-transition-name: modal; opacity: 1; transform: scale(1); transition: opacity 0.3s, transform 0.3s; } /* 11. REAL-WORLD EXAMPLE: Image gallery */ .gallery { contain: layout; /* Enable scoped transitions */ } .gallery-item { view-transition-name: auto; /* Unique name per item */ } .gallery::view-transition-old(gallery-item), .gallery::view-transition-new(gallery-item) { animation-duration: 0.5s; animation-timing-function: ease-in-out; } /* Morph effect */ .gallery::view-transition-group(gallery-item) { animation-duration: 0.5s; } /* 12. CONDITIONAL TRANSITIONS - Only for specific states */ .card[data-state="expanded"] { view-transition-name: expanded-card; } ::view-transition-group(expanded-card) { animation-duration: 0.6s; z-index: 10; /* Ensure it's on top */ } /* 13. MULTIPLE CONCURRENT SCOPED TRANSITIONS (Chrome 140+) */ /* Navigation transition */ nav { contain: layout; } nav .item { view-transition-name: nav-item; } /* Sidebar transition (can run simultaneously!) */ .sidebar { contain: layout; } .sidebar .widget { view-transition-name: widget; } /* 14. ACCESSIBILITY - Respect reduced motion */ @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation-duration: 0.001s !important; animation-delay: 0s !important; } } /* 15. PROGRESSIVE ENHANCEMENT */ @supports not (view-transition-name: none) { /* Fallback for browsers without view transitions */ .card { transition: transform 0.3s ease, opacity 0.3s ease; } .card.hidden { opacity: 0; transform: scale(0.9); } } /* 16. DEBUG - Visualize transition groups */ ::view-transition-group(*) { /* outline: 2px solid red; */ /* Uncomment to see transition boundaries */ } /* JavaScript usage (for reference): * * // Document-scoped transition * document.startViewTransition(() => { * // Make DOM changes here * element.classList.add('active'); * }); * * // Element-scoped transition (Chrome 140+) * const nav = document.querySelector('nav'); * nav.startViewTransition(() => { * // Changes only within nav element * nav.innerHTML = newContent; * }); */ /* References: * MDN: https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition * Spec: https://drafts.csswg.org/css-view-transitions/ * Scoped: https://developer.chrome.com/blog/scoped-view-transitions-feedback * Browser Support: Chrome 111+, Edge 111+ (document), Chrome 140+ (scoped) */` }, '::view-transition': { basic_usage: '::view-transition { /* styles */ }', best_practices: [ 'Root pseudo-element for the entire transition tree', 'Use for global transition container styles', 'In scoped transitions (Chrome 140+), attached to the transition root element', 'Automatically positioned to cover the viewport or scoped container', 'Contains all ::view-transition-group() children' ], fallbacks: [ 'Use container elements with CSS animations', 'Implement custom overlay solutions', 'Use @supports for feature detection' ], example_code: ` /* Root of the view transition pseudo-element tree */ ::view-transition { background-color: transparent; /* Usually don't need to modify this */ } /* SCOPED TRANSITION - Root attached to specific element */ .modal::view-transition { background: oklch(0% 0 0 / 0.5); backdrop-filter: blur(4px); } /* Ensure transitions are above other content */ ::view-transition { z-index: 999; } /* Custom styling for the transition container */ nav::view-transition { border-radius: 8px; overflow: hidden; }` }, '::view-transition-group()': { basic_usage: '::view-transition-group(card) { animation-duration: 0.5s; }', best_practices: [ 'Animates position and size between old and new states', 'Use () for specific named groups', 'Use (*) wildcard to target all groups', 'Default animation: interpolate size and position', 'Customize timing, easing, and duration', 'Each unique view-transition-name creates a group' ], fallbacks: [ 'Use JavaScript for cross-browser morphing effects', 'Implement CSS transform-based animations', 'Use @supports for feature detection' ], example_code: ` /* Target specific transition group */ ::view-transition-group(card) { animation-duration: 0.5s; animation-timing-function: ease-in-out; } /* Target all groups with wildcard */ ::view-transition-group(*) { animation-duration: 0.4s; } /* Different timing for different groups */ ::view-transition-group(header) { animation-duration: 0.3s; } ::view-transition-group(content) { animation-duration: 0.6s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } /* Control z-index for layering */ ::view-transition-group(modal) { z-index: 100; } ::view-transition-group(background) { z-index: 1; } /* Custom transform origin */ ::view-transition-group(thumbnail) { transform-origin: top left; } /* Scoped transition groups */ .gallery::view-transition-group(image) { animation-duration: 0.5s; }` } }; export function getAnimationGuidance(property: string, needsFallback: boolean = false): ImplementationGuidance | null { const guidance = ANIMATION_GUIDANCE[property]; if (!guidance) return null; return { ...guidance, fallbacks: needsFallback ? guidance.fallbacks : [] }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Luko248/css-first'

If you have feedback or need assistance with the MCP directory API, please join our Discord server