Animation and Keyframe Generation
1. Keyframe Mixins and Animation Libraries
| Pattern | Technique | Output | Use Case |
|---|---|---|---|
| Keyframe Mixin | @mixin with @keyframes | Reusable animations | Common effects |
| Animation Shorthand | Single-line animation setup | Quick application | Simple animations |
| Animation Library | Pre-built animation set | Ready-to-use effects | Rapid development |
| Prefix Handling | Vendor prefix automation | Cross-browser support | Legacy browsers |
| Composable Animations | Combine multiple effects | Complex movements | Rich interactions |
Example: Basic keyframe animation mixins
// Reusable keyframe mixin
@mixin keyframes($name) {
@keyframes #{$name} {
@content;
}
}
// Usage - Fade in animation
@include keyframes(fadeIn) {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
// Slide in from left
@include keyframes(slideInLeft) {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
// Bounce effect
@include keyframes(bounce) {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
}
// Apply animation
.element {
animation: fadeIn 0.5s ease-out;
}
.slide {
animation: slideInLeft 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.bouncing {
animation: bounce 1s infinite;
}
Example: Animation library with mixin helpers
// Animation application mixin
@mixin animate(
$name,
$duration: 0.3s,
$timing: ease,
$delay: 0s,
$iteration: 1,
$direction: normal,
$fill: both
) {
animation-name: $name;
animation-duration: $duration;
animation-timing-function: $timing;
animation-delay: $delay;
animation-iteration-count: $iteration;
animation-direction: $direction;
animation-fill-mode: $fill;
}
// Shorthand animation
@mixin anim($name, $duration: 0.3s, $timing: ease) {
animation: $name $duration $timing;
}
// Fade animations
@include keyframes(fadeIn) {
from { opacity: 0; }
to { opacity: 1; }
}
@include keyframes(fadeOut) {
from { opacity: 1; }
to { opacity: 0; }
}
@include keyframes(fadeInUp) {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@include keyframes(fadeInDown) {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
// Scale animations
@include keyframes(zoomIn) {
from {
opacity: 0;
transform: scale(0.3);
}
to {
opacity: 1;
transform: scale(1);
}
}
@include keyframes(pulse) {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
// Rotation animations
@include keyframes(rotate) {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@include keyframes(shake) {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); }
20%, 40%, 60%, 80% { transform: translateX(10px); }
}
// Usage
.modal {
@include animate(fadeInUp, 0.4s, cubic-bezier(0.4, 0, 0.2, 1));
}
.loader {
@include anim(rotate, 1s, linear);
animation-iteration-count: infinite;
}
.error-message {
@include anim(shake, 0.5s, ease-in-out);
}
Example: Advanced animation library patterns
// Configurable entrance animations
@mixin entrance-animation(
$name,
$from-opacity: 0,
$from-transform: translateY(20px),
$to-opacity: 1,
$to-transform: translateY(0)
) {
@include keyframes($name) {
from {
opacity: $from-opacity;
transform: $from-transform;
}
to {
opacity: $to-opacity;
transform: $to-transform;
}
}
}
// Generate entrance variations
@include entrance-animation('enterFromBottom', 0, translateY(50px));
@include entrance-animation('enterFromTop', 0, translateY(-50px));
@include entrance-animation('enterFromLeft', 0, translateX(-50px));
@include entrance-animation('enterFromRight', 0, translateX(50px));
// Attention seekers
@include keyframes(heartbeat) {
0%, 100% {
transform: scale(1);
}
10%, 30% {
transform: scale(0.9);
}
20%, 40% {
transform: scale(1.1);
}
}
@include keyframes(wiggle) {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-5deg); }
75% { transform: rotate(5deg); }
}
@include keyframes(flash) {
0%, 50%, 100% { opacity: 1; }
25%, 75% { opacity: 0; }
}
// Loading animations
@include keyframes(spin) {
to { transform: rotate(360deg); }
}
@include keyframes(dots) {
0%, 20% { content: '.'; }
40% { content: '..'; }
60%, 100% { content: '...'; }
}
// Utility mixin for animation delays
@mixin stagger-animation($base-delay: 0.1s, $count: 5) {
@for $i from 1 through $count {
&:nth-child(#{$i}) {
animation-delay: $base-delay * ($i - 1);
}
}
}
// Usage
.list-item {
@include anim(fadeInUp, 0.4s, ease-out);
@include stagger-animation(0.1s, 10);
}
2. Dynamic Animation Generation with Maps
| Technique | Data Structure | Benefit | Use Case |
|---|---|---|---|
| Map-based Config | Animation properties map | Centralized settings | Consistent animations |
| Loop Generation | @each over animation map | Auto-generate variants | Multiple animations |
| Timing Presets | Easing function map | Reusable timing | Brand consistency |
| Duration Scale | Speed multiplier map | Consistent timing | Animation speeds |
| State Animations | State-to-animation map | Logical mapping | UI state changes |
Example: Map-based animation system
// Animation configuration map
$animations: (
'fade-in': (
from: (opacity: 0),
to: (opacity: 1),
duration: 0.3s,
timing: ease-out
),
'slide-up': (
from: (opacity: 0, transform: translateY(20px)),
to: (opacity: 1, transform: translateY(0)),
duration: 0.4s,
timing: cubic-bezier(0.4, 0, 0.2, 1)
),
'scale-in': (
from: (opacity: 0, transform: scale(0.8)),
to: (opacity: 1, transform: scale(1)),
duration: 0.35s,
timing: cubic-bezier(0.34, 1.56, 0.64, 1)
),
'rotate-in': (
from: (opacity: 0, transform: rotate(-180deg) scale(0.5)),
to: (opacity: 1, transform: rotate(0deg) scale(1)),
duration: 0.6s,
timing: ease-out
)
);
// Generate keyframes from map
@each $name, $config in $animations {
@include keyframes($name) {
from {
@each $prop, $value in map-get($config, from) {
#{$prop}: $value;
}
}
to {
@each $prop, $value in map-get($config, to) {
#{$prop}: $value;
}
}
}
// Generate utility class
.animate-#{$name} {
animation: $name
map-get($config, duration)
map-get($config, timing);
}
}
// Output:
// @keyframes fade-in { ... }
// .animate-fade-in { animation: fade-in 0.3s ease-out; }
// @keyframes slide-up { ... }
// .animate-slide-up { animation: slide-up 0.4s cubic-bezier(...); }
// etc.
Example: Timing function and duration scales
// Easing function library
$easings: (
'linear': linear,
'ease': ease,
'ease-in': ease-in,
'ease-out': ease-out,
'ease-in-out': ease-in-out,
'ease-in-quad': cubic-bezier(0.55, 0.085, 0.68, 0.53),
'ease-out-quad': cubic-bezier(0.25, 0.46, 0.45, 0.94),
'ease-in-cubic': cubic-bezier(0.55, 0.055, 0.675, 0.19),
'ease-out-cubic': cubic-bezier(0.215, 0.61, 0.355, 1),
'ease-in-quart': cubic-bezier(0.895, 0.03, 0.685, 0.22),
'ease-out-quart': cubic-bezier(0.165, 0.84, 0.44, 1),
'ease-in-back': cubic-bezier(0.6, -0.28, 0.735, 0.045),
'ease-out-back': cubic-bezier(0.175, 0.885, 0.32, 1.275),
'ease-in-out-back': cubic-bezier(0.68, -0.55, 0.265, 1.55)
);
// Duration scale
$durations: (
'instant': 0.1s,
'fast': 0.2s,
'normal': 0.3s,
'slow': 0.5s,
'slower': 0.8s,
'slowest': 1.2s
);
// Helper function to get easing
@function ease($name) {
@return map-get($easings, $name);
}
// Helper function to get duration
@function duration($name) {
@return map-get($durations, $name);
}
// Animation mixin using presets
@mixin preset-animate($name, $duration-key: 'normal', $easing-key: 'ease-out') {
animation: $name duration($duration-key) ease($easing-key);
}
// Generate timing utility classes
@each $name, $value in $easings {
.ease-#{$name} {
animation-timing-function: $value;
}
}
@each $name, $value in $durations {
.duration-#{$name} {
animation-duration: $value;
}
}
// Usage
.modal {
@include preset-animate(fadeIn, 'normal', 'ease-out-back');
}
.notification {
animation: slideInRight duration('fast') ease('ease-out-quad');
}
// HTML usage:
// <div class="animate-fade-in duration-slow ease-ease-out-back">
Example: Complex animation sequences with maps
// Multi-step animation configuration
$complex-animations: (
'loading-pulse': (
steps: (
0: (transform: scale(1), opacity: 1),
50: (transform: scale(1.2), opacity: 0.7),
100: (transform: scale(1), opacity: 1)
),
duration: 1.5s,
timing: ease-in-out,
iteration: infinite
),
'success-check': (
steps: (
0: (transform: scale(0) rotate(0deg), opacity: 0),
50: (transform: scale(1.2) rotate(360deg), opacity: 1),
100: (transform: scale(1) rotate(360deg), opacity: 1)
),
duration: 0.6s,
timing: cubic-bezier(0.68, -0.55, 0.265, 1.55),
iteration: 1
),
'error-shake': (
steps: (
0: (transform: translateX(0)),
10: (transform: translateX(-10px)),
20: (transform: translateX(10px)),
30: (transform: translateX(-10px)),
40: (transform: translateX(10px)),
50: (transform: translateX(-10px)),
60: (transform: translateX(10px)),
70: (transform: translateX(-10px)),
80: (transform: translateX(10px)),
90: (transform: translateX(-10px)),
100: (transform: translateX(0))
),
duration: 0.8s,
timing: ease,
iteration: 1
)
);
// Generate complex keyframes
@each $name, $config in $complex-animations {
@include keyframes($name) {
@each $percent, $properties in map-get($config, steps) {
#{$percent}% {
@each $prop, $value in $properties {
#{$prop}: $value;
}
}
}
}
.#{$name} {
animation-name: $name;
animation-duration: map-get($config, duration);
animation-timing-function: map-get($config, timing);
animation-iteration-count: map-get($config, iteration);
}
}
3. CSS Transition Mixins and Timing Functions
| Mixin | Properties | Use Case | Example |
|---|---|---|---|
| transition | Single property transition | Simple effects | Button hover |
| transitions | Multiple properties | Complex changes | Card expand |
| transition-all | All properties | Quick prototyping | Theme changes |
| Custom easing | Cubic bezier curves | Brand feel | Material Design |
| Hardware acceleration | transform, opacity | Performance | Smooth animations |
Example: Transition utility mixins
// Single property transition
@mixin transition($property, $duration: 0.3s, $timing: ease, $delay: 0s) {
transition: $property $duration $timing $delay;
}
// Multiple property transitions
@mixin transitions($transitions...) {
transition: $transitions;
}
// Transition all properties
@mixin transition-all($duration: 0.3s, $timing: ease) {
transition: all $duration $timing;
}
// No transition
@mixin no-transition {
transition: none !important;
}
// Hardware-accelerated properties only
@mixin hw-transition($duration: 0.3s, $timing: ease) {
transition: transform $duration $timing,
opacity $duration $timing;
}
// Usage examples
.button {
@include transition(background-color, 0.2s, ease-out);
&:hover {
background-color: darken(#1976d2, 10%);
}
}
.card {
@include transitions(
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s ease-out,
background-color 0.2s ease
);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
}
}
.modal {
@include hw-transition(0.4s, cubic-bezier(0.4, 0, 0.2, 1));
}
// Disable transitions for reduced motion
@media (prefers-reduced-motion: reduce) {
* {
@include no-transition;
animation-duration: 0.01ms !important;
}
}
Example: Timing function library with presets
// Timing function presets
$timing-functions: (
// Standard
'linear': linear,
'ease': ease,
'ease-in': ease-in,
'ease-out': ease-out,
'ease-in-out': ease-in-out,
// Material Design
'material-standard': cubic-bezier(0.4, 0, 0.2, 1),
'material-decelerate': cubic-bezier(0, 0, 0.2, 1),
'material-accelerate': cubic-bezier(0.4, 0, 1, 1),
'material-sharp': cubic-bezier(0.4, 0, 0.6, 1),
// Expressive
'bounce': cubic-bezier(0.68, -0.55, 0.265, 1.55),
'elastic': cubic-bezier(0.68, -0.6, 0.32, 1.6),
'overshoot': cubic-bezier(0.175, 0.885, 0.32, 1.275),
// Smooth
'smooth': cubic-bezier(0.25, 0.1, 0.25, 1),
'smooth-in': cubic-bezier(0.42, 0, 1, 1),
'smooth-out': cubic-bezier(0, 0, 0.58, 1),
'smooth-in-out': cubic-bezier(0.42, 0, 0.58, 1)
);
// Get timing function
@function timing($name) {
@if map-has-key($timing-functions, $name) {
@return map-get($timing-functions, $name);
}
@warn "Timing function '#{$name}' not found.";
@return ease;
}
// Transition with preset timing
@mixin t($property, $duration: 0.3s, $timing-name: 'ease-out', $delay: 0s) {
transition: $property $duration timing($timing-name) $delay;
}
// Generate timing utility classes
@each $name, $function in $timing-functions {
.timing-#{$name} {
transition-timing-function: $function;
}
}
// Usage
.button {
@include t(all, 0.2s, 'material-standard');
}
.dropdown {
@include t(opacity, 0.15s, 'material-decelerate');
@include t(transform, 0.3s, 'material-decelerate');
}
.notification {
transition: transform 0.4s timing('bounce'),
opacity 0.3s timing('ease-out');
}
Example: Advanced transition patterns
// Stagger transition delays
@mixin stagger-transition(
$property: all,
$duration: 0.3s,
$timing: ease-out,
$base-delay: 0.05s,
$count: 10
) {
transition: $property $duration $timing;
@for $i from 1 through $count {
&:nth-child(#{$i}) {
transition-delay: $base-delay * ($i - 1);
}
}
}
// Conditional transitions (only specific properties)
@mixin transition-props($props, $duration: 0.3s, $timing: ease) {
$transition-list: ();
@each $prop in $props {
$transition-list: append($transition-list, $prop $duration $timing, comma);
}
transition: $transition-list;
}
// Safe transitions (avoid layout thrashing)
@mixin safe-transition($duration: 0.3s, $timing: ease) {
// Only transition properties that don't trigger layout
transition: transform $duration $timing,
opacity $duration $timing,
color $duration $timing,
background-color $duration $timing,
border-color $duration $timing,
box-shadow $duration $timing;
}
// Transition with will-change optimization
@mixin optimized-transition($property, $duration: 0.3s, $timing: ease) {
will-change: $property;
transition: $property $duration $timing;
&:hover,
&:focus {
will-change: auto;
}
}
// Usage
.list-item {
@include stagger-transition(transform, 0.3s, ease-out, 0.05s, 20);
}
.card {
@include transition-props((transform, box-shadow), 0.3s, ease-out);
}
.menu-item {
@include safe-transition(0.2s, ease-out);
}
.hero-image {
@include optimized-transition(transform, 0.6s, ease-out);
}
4. Transform Function Utilities
| Transform | Function | Use Case | Performance |
|---|---|---|---|
| translate | translateX/Y/Z | Movement | GPU accelerated Fast |
| scale | scaleX/Y/Z | Zoom effects | GPU accelerated Fast |
| rotate | rotateX/Y/Z | Rotation | GPU accelerated Fast |
| skew | skewX/Y | Distortion | GPU accelerated Fast |
| 3D transforms | translate3d, etc. | Depth effects | Hardware accelerated Fast |
Example: Transform utility mixins
// Center with transform
@mixin center-transform {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// Center horizontally
@mixin center-x {
position: absolute;
left: 50%;
transform: translateX(-50%);
}
// Center vertically
@mixin center-y {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
// Hardware acceleration hint
@mixin hardware-accelerate {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
// 3D card flip
@mixin card-flip($duration: 0.6s) {
transform-style: preserve-3d;
transition: transform $duration;
.front,
.back {
backface-visibility: hidden;
position: absolute;
width: 100%;
height: 100%;
}
.back {
transform: rotateY(180deg);
}
&.flipped {
transform: rotateY(180deg);
}
}
// Parallax effect
@mixin parallax($speed: 0.5) {
transform: translateY(calc(var(--scroll) * #{$speed}px));
}
// Usage
.modal-content {
@include center-transform;
}
.dropdown-menu {
@include center-x;
top: 100%;
}
.card {
@include hardware-accelerate;
&:hover {
transform: translateY(-4px) translateZ(0);
}
}
.flip-card {
@include card-flip(0.6s);
}
.parallax-bg {
@include parallax(0.3);
}
Example: Complex transform combinations
// Transform builder function
@function build-transform($transforms...) {
$result: '';
@each $transform in $transforms {
$result: $result + ' ' + $transform;
}
@return unquote($result);
}
// Common transform combinations
@mixin lift($distance: 8px, $scale: 1.02) {
transform: translateY(-#{$distance}) scale($scale);
}
@mixin push($distance: 2px) {
transform: translateY($distance) scale(0.98);
}
@mixin swing {
transform-origin: top center;
@include keyframes(swing) {
20% { transform: rotate(15deg); }
40% { transform: rotate(-10deg); }
60% { transform: rotate(5deg); }
80% { transform: rotate(-5deg); }
100% { transform: rotate(0deg); }
}
}
@mixin wobble {
@include keyframes(wobble) {
0%, 100% { transform: translateX(0) rotate(0); }
15% { transform: translateX(-25px) rotate(-5deg); }
30% { transform: translateX(20px) rotate(3deg); }
45% { transform: translateX(-15px) rotate(-3deg); }
60% { transform: translateX(10px) rotate(2deg); }
75% { transform: translateX(-5px) rotate(-1deg); }
}
}
// 3D transforms
@mixin rotate-3d($x: 0, $y: 1, $z: 0, $angle: 45deg) {
transform: rotate3d($x, $y, $z, $angle);
}
@mixin flip-horizontal {
transform: scaleX(-1);
}
@mixin flip-vertical {
transform: scaleY(-1);
}
// Perspective helper
@mixin perspective($distance: 1000px) {
perspective: $distance;
perspective-origin: center;
}
// Usage
.card {
transition: transform 0.3s ease-out;
&:hover {
@include lift(10px, 1.05);
}
&:active {
@include push(4px);
}
}
.badge {
animation: swing 1s ease-in-out;
}
.container-3d {
@include perspective(1200px);
.item {
@include rotate-3d(1, 0, 0, 15deg);
}
}
.mirrored {
@include flip-horizontal;
}
Example: Transform animation sequences
// Entrance animations with transforms
@mixin slide-enter($from: left, $distance: 100%, $duration: 0.4s) {
@if $from == left {
@include keyframes(slideEnterLeft) {
from { transform: translateX(-#{$distance}); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
animation: slideEnterLeft $duration ease-out;
} @else if $from == right {
@include keyframes(slideEnterRight) {
from { transform: translateX($distance); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
animation: slideEnterRight $duration ease-out;
} @else if $from == top {
@include keyframes(slideEnterTop) {
from { transform: translateY(-#{$distance}); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
animation: slideEnterTop $duration ease-out;
} @else if $from == bottom {
@include keyframes(slideEnterBottom) {
from { transform: translateY($distance); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
animation: slideEnterBottom $duration ease-out;
}
}
// Scale entrance
@mixin scale-enter($from: 0.8, $duration: 0.3s, $timing: ease-out) {
@include keyframes(scaleEnter) {
from {
transform: scale($from);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
animation: scaleEnter $duration $timing;
}
// Rotate entrance
@mixin rotate-enter($degrees: 90deg, $duration: 0.5s) {
@include keyframes(rotateEnter) {
from {
transform: rotate($degrees) scale(0.5);
opacity: 0;
}
to {
transform: rotate(0deg) scale(1);
opacity: 1;
}
}
animation: rotateEnter $duration cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
// Usage
.sidebar {
@include slide-enter(left, 300px, 0.4s);
}
.modal {
@include scale-enter(0.7, 0.3s, cubic-bezier(0.34, 1.56, 0.64, 1));
}
.notification {
@include rotate-enter(180deg, 0.6s);
}
5. Animation State Management
| State | CSS Property | Control | Use Case |
|---|---|---|---|
| Play/Pause | animation-play-state | paused/running | User control |
| Direction | animation-direction | normal/reverse/alternate | Reversible animations |
| Fill Mode | animation-fill-mode | forwards/backwards/both | End state retention |
| Iteration | animation-iteration-count | number/infinite | Looping control |
| Delay | animation-delay | Time value | Sequence timing |
Example: Animation state control mixins
// Pause animation on hover
@mixin pause-on-hover {
&:hover {
animation-play-state: paused;
}
}
// Play animation on hover
@mixin play-on-hover($name, $duration: 1s, $timing: ease) {
animation: $name $duration $timing paused;
&:hover {
animation-play-state: running;
}
}
// Reverse on second hover
@mixin reverse-on-second-hover($name, $duration: 0.5s) {
animation: $name $duration ease-out;
animation-fill-mode: both;
&.reversed {
animation-direction: reverse;
}
}
// Animation with state classes
@mixin stateful-animation(
$name,
$duration: 0.5s,
$timing: ease
) {
animation: $name $duration $timing;
animation-fill-mode: both;
animation-play-state: paused;
&.playing {
animation-play-state: running;
}
&.reverse {
animation-direction: reverse;
}
&.loop {
animation-iteration-count: infinite;
}
}
// Sequential animations
@mixin sequence($animations...) {
$total-duration: 0s;
@each $anim in $animations {
$name: nth($anim, 1);
$duration: nth($anim, 2);
$delay: $total-duration;
&.step-#{index($animations, $anim)} {
animation: $name $duration ease-out $delay;
animation-fill-mode: both;
}
$total-duration: $total-duration + $duration;
}
}
// Usage
.spinner {
@include pause-on-hover;
}
.icon {
@include play-on-hover(bounce, 0.6s, ease-in-out);
}
.menu {
@include stateful-animation(slideDown, 0.4s, ease-out);
}
// JavaScript adds/removes classes:
// element.classList.add('playing');
// element.classList.add('reverse');
// element.classList.add('loop');
Example: Animation orchestration
// Chain animations using animation-delay
@mixin animation-chain($animations) {
$total-delay: 0s;
$all-animations: ();
@each $name, $duration in $animations {
$all-animations: append(
$all-animations,
$name $duration ease-out $total-delay both,
comma
);
$total-delay: $total-delay + $duration;
}
animation: $all-animations;
}
// Staggered group animation
@mixin stagger-group(
$animation,
$duration: 0.3s,
$delay: 0.1s,
$count: 10
) {
animation: $animation $duration ease-out;
animation-fill-mode: both;
@for $i from 1 through $count {
&:nth-child(#{$i}) {
animation-delay: ($i - 1) * $delay;
}
}
}
// Coordinated entrance
@mixin coordinated-entrance($children-map) {
@each $selector, $config in $children-map {
#{$selector} {
animation: map-get($config, animation)
map-get($config, duration)
map-get($config, timing)
map-get($config, delay);
animation-fill-mode: both;
}
}
}
// Usage - Chain multiple animations
.logo {
@include animation-chain((
fadeIn: 0.5s,
slideUp: 0.4s,
bounce: 0.6s
));
}
// Staggered list items
.nav-item {
@include stagger-group(fadeInLeft, 0.4s, 0.08s, 8);
}
// Coordinated page entrance
.hero {
@include coordinated-entrance((
'.hero-title': (
animation: fadeInUp,
duration: 0.6s,
timing: ease-out,
delay: 0.2s
),
'.hero-subtitle': (
animation: fadeInUp,
duration: 0.6s,
timing: ease-out,
delay: 0.4s
),
'.hero-cta': (
animation: scaleIn,
duration: 0.5s,
timing: cubic-bezier(0.68, -0.55, 0.265, 1.55),
delay: 0.8s
)
));
}
Example: Event-driven animation states
// Loading state animations
@mixin loading-state($animation: pulse) {
&.loading {
animation: $animation 1.5s ease-in-out infinite;
pointer-events: none;
& > * {
opacity: 0.6;
}
}
}
// Success/error state transitions
@mixin result-states {
transition: all 0.3s ease-out;
&.success {
animation: successPulse 0.6s ease-out;
background-color: #4caf50;
color: white;
}
&.error {
animation: errorShake 0.5s ease-in-out;
background-color: #f44336;
color: white;
}
}
// Define state animations
@include keyframes(successPulse) {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
@include keyframes(errorShake) {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-8px); }
20%, 40%, 60%, 80% { transform: translateX(8px); }
}
// Focus/blur animations
@mixin focus-animation($scale: 1.02) {
transition: transform 0.2s ease-out;
&:focus {
transform: scale($scale);
outline: 2px solid #2196f3;
outline-offset: 2px;
}
}
// Hover intent (delayed hover)
@mixin hover-intent($delay: 0.3s) {
&:hover {
animation: hoverIntent $delay ease-out;
animation-fill-mode: forwards;
}
@include keyframes(hoverIntent) {
from { opacity: 1; }
to { opacity: 1; transform: translateY(-4px); }
}
}
// Usage
.submit-button {
@include loading-state(rotate);
@include result-states;
}
.input-field {
@include focus-animation(1.02);
}
.card {
@include hover-intent(0.2s);
}
6. Performance-optimized Animation Patterns
| Technique | Implementation | Benefit | Impact |
|---|---|---|---|
| GPU Acceleration | transform, opacity only | 60fps animations | Smooth performance |
| will-change | Hint to browser | Pre-optimization | Faster start |
| contain | Isolation hint | Limit repaints | Better performance |
| Reduced Motion | prefers-reduced-motion | Accessibility | Better UX |
| Layer Promotion | translateZ(0) | Own composite layer | Isolated rendering |
Example: GPU-accelerated animations
// Force GPU acceleration
@mixin gpu-accelerate {
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
will-change: transform;
}
// Performant transform animation
@mixin performant-transform($property: transform, $duration: 0.3s) {
@include gpu-accelerate;
transition: $property $duration cubic-bezier(0.4, 0, 0.2, 1);
}
// Only animate GPU-friendly properties
@mixin gpu-animation {
// ✅ GOOD: transform and opacity (GPU accelerated)
transition: transform 0.3s ease-out,
opacity 0.3s ease-out;
// ❌ AVOID: width, height, top, left (trigger layout)
// transition: width 0.3s, height 0.3s, top 0.3s;
}
// Layer promotion for animations
@mixin promote-layer {
will-change: transform, opacity;
// Remove after animation
&.animated {
will-change: auto;
}
}
// Composite layer for smooth animations
@mixin composite-layer {
// Create new stacking context
position: relative;
z-index: 0;
// Promote to own layer
transform: translateZ(0);
// Contain paint and layout
contain: layout paint;
}
// Usage
.smooth-card {
@include performant-transform(transform, 0.4s);
&:hover {
transform: translateY(-8px) scale(1.02);
}
}
.modal {
@include composite-layer;
@include gpu-animation;
}
Example: Accessibility and reduced motion
// Respect user's motion preferences
@mixin respect-motion-preference {
@media (prefers-reduced-motion: reduce) {
animation: none !important;
transition: none !important;
}
}
// Safe animation (automatically disabled for reduced motion)
@mixin safe-animate(
$name,
$duration: 0.3s,
$timing: ease,
$fallback-transition: opacity 0.1s
) {
animation: $name $duration $timing;
@media (prefers-reduced-motion: reduce) {
animation: none;
transition: $fallback-transition;
}
}
// Crossfade instead of movement
@mixin reduced-motion-friendly($normal-anim, $reduced-anim: fadeIn) {
animation: $normal-anim;
@media (prefers-reduced-motion: reduce) {
animation: $reduced-anim 0.2s ease-out;
}
}
// Conditional animation
@mixin motion-safe($animation, $duration: 0.3s) {
@media (prefers-reduced-motion: no-preference) {
animation: $animation $duration ease-out;
}
}
// Global reduced motion reset
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
// Usage
.card {
@include safe-animate(slideInUp, 0.4s, ease-out, opacity 0.2s);
}
.notification {
@include reduced-motion-friendly(slideInRight, fadeIn);
}
.hero {
@include motion-safe(fadeInUp, 0.6s);
}
Example: Performance monitoring and optimization
// Optimized animation helper
@mixin optimized-animation(
$name,
$duration: 0.3s,
$timing: ease-out,
$will-change-props: transform opacity
) {
// Enable will-change before animation
will-change: $will-change-props;
animation: $name $duration $timing;
// Cleanup after animation
animation-fill-mode: both;
}
// Debounced animation (prevent too-frequent triggers)
@mixin debounced-animation($name, $duration: 0.3s, $delay: 0.1s) {
animation: $name $duration ease-out;
animation-delay: $delay;
animation-fill-mode: both;
}
// Budget-aware animations (simple on low-end devices)
@mixin progressive-animation(
$simple-animation,
$complex-animation,
$duration: 0.4s
) {
// Default: simple animation
animation: $simple-animation $duration ease-out;
// Enhanced on powerful devices (no standard query yet, pseudo-code)
@supports (backdrop-filter: blur(10px)) {
animation: $complex-animation $duration cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
}
// Limit animation complexity based on number of elements
@mixin scale-animation-complexity($threshold: 20) {
animation: complexAnimation 0.6s ease-out;
// Simplify if many elements
&:nth-child(n + #{$threshold}) {
animation: simpleAnimation 0.3s ease-out;
}
}
// Container-based performance hints
@mixin contained-animation {
// Limit repaints to element
contain: layout style paint;
// Isolate rendering
isolation: isolate;
// Create stacking context
position: relative;
z-index: 0;
}
// Usage
.hero-image {
@include optimized-animation(zoomIn, 0.8s, ease-out, transform opacity);
}
.tooltip {
@include debounced-animation(fadeIn, 0.2s, 0.3s);
}
.product-card {
@include progressive-animation(fadeIn, rotateInWithBounce, 0.5s);
}
.animated-list-item {
@include scale-animation-complexity(50);
}
.animation-container {
@include contained-animation;
}
Animation Best Practices
- Use transform and opacity: GPU-accelerated properties for 60fps animations
- Avoid layout thrashing: Don't animate width, height, top, left, margin, padding
- will-change sparingly: Use only when needed, remove after animation
- Respect reduced motion: Always provide @media (prefers-reduced-motion) fallbacks
- Keep it short: Animations under 500ms feel snappier; avoid long durations
- Ease-out for entrances: ease-in for exits, ease-in-out for transitions
- Stagger for groups: Use delays for sequential animations in lists
- Hardware acceleration: Use translateZ(0) for smooth performance
Note: Always test animations on lower-end devices. Use
transform and
opacity exclusively for 60fps performance. Provide meaningful motion
with prefers-reduced-motion support for accessibility.