Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Enforce Vue 3 best practices—Composition API, script setup, TypeScript, component boundaries, and reactivity patterns
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/animation-state-driven-technique.md
1---2title: State-driven Animations with CSS Transitions and Style Bindings3impact: LOW4impactDescription: Combining Vue's reactive style bindings with CSS transitions creates smooth, interactive animations5type: best-practice6tags: [vue3, animation, css, transition, style-binding, state, interactive]7---89# State-driven Animations with CSS Transitions and Style Bindings1011**Impact: LOW** - For responsive, interactive animations that react to user input or state changes, combine Vue's dynamic style bindings with CSS transitions. This creates smooth animations that interpolate values in real-time based on state.1213## Task List1415- Use `:style` binding for dynamic properties that change frequently16- Add CSS `transition` property to smoothly animate between values17- Consider using `transform` and `opacity` for GPU-accelerated animations18- For complex value interpolation, use watchers with animation libraries1920## Basic Pattern2122```vue23<template>24<div25@mousemove="onMousemove"26:style="{ backgroundColor: `hsl(${hue}, 80%, 50%)` }"27class="interactive-area"28>29<p>Move your mouse across this div...</p>30<p>Hue: {{ hue }}</p>31</div>32</template>3334<script setup>35import { ref } from 'vue'3637const hue = ref(0)3839function onMousemove(e) {40// Map mouse X position to hue (0-360)41const rect = e.currentTarget.getBoundingClientRect()42hue.value = Math.round((e.clientX - rect.left) / rect.width * 360)43}44</script>4546<style>47.interactive-area {48transition: background-color 0.3s ease;49height: 200px;50display: flex;51flex-direction: column;52align-items: center;53justify-content: center;54}55</style>56```5758## Common Use Cases5960### Following Mouse Position6162```vue63<template>64<div65class="container"66@mousemove="onMousemove"67>68<div69class="follower"70:style="{71transform: `translate(${x}px, ${y}px)`72}"73/>74</div>75</template>7677<script setup>78import { ref } from 'vue'7980const x = ref(0)81const y = ref(0)8283function onMousemove(e) {84const rect = e.currentTarget.getBoundingClientRect()85x.value = e.clientX - rect.left86y.value = e.clientY - rect.top87}88</script>8990<style>91.container {92position: relative;93height: 300px;94}9596.follower {97position: absolute;98width: 20px;99height: 20px;100background: blue;101border-radius: 50%;102/* Smooth following with transition */103transition: transform 0.1s ease-out;104/* Prevent the follower from triggering mousemove */105pointer-events: none;106}107</style>108```109110### Progress Animation111112```vue113<template>114<div class="progress-container">115<div116class="progress-bar"117:style="{ width: `${progress}%` }"118/>119</div>120<input121type="range"122v-model.number="progress"123min="0"124max="100"125/>126</template>127128<script setup>129import { ref } from 'vue'130131const progress = ref(0)132</script>133134<style>135.progress-container {136height: 20px;137background: #e0e0e0;138border-radius: 10px;139overflow: hidden;140}141142.progress-bar {143height: 100%;144background: linear-gradient(90deg, #4CAF50, #8BC34A);145transition: width 0.3s ease;146}147</style>148```149150### Scroll-based Animation151152```vue153<template>154<div155class="hero"156:style="{157opacity: heroOpacity,158transform: `translateY(${scrollOffset}px)`159}"160>161<h1>Scroll Down</h1>162</div>163</template>164165<script setup>166import { ref, computed, onMounted, onUnmounted } from 'vue'167168const scrollY = ref(0)169170const heroOpacity = computed(() => {171return Math.max(0, 1 - scrollY.value / 300)172})173174const scrollOffset = computed(() => {175return scrollY.value * 0.5 // Parallax effect176})177178function handleScroll() {179scrollY.value = window.scrollY180}181182onMounted(() => {183window.addEventListener('scroll', handleScroll, { passive: true })184})185186onUnmounted(() => {187window.removeEventListener('scroll', handleScroll)188})189</script>190191<style>192.hero {193height: 100vh;194display: flex;195align-items: center;196justify-content: center;197/* Note: No transition for scroll-based animations - they should be instant */198}199</style>200```201202### Color Theme Transition203204```vue205<template>206<div207class="app"208:style="themeStyles"209>210<button @click="toggleTheme">Toggle Theme</button>211<p>Current theme: {{ isDark ? 'Dark' : 'Light' }}</p>212</div>213</template>214215<script setup>216import { ref, computed } from 'vue'217218const isDark = ref(false)219220const themeStyles = computed(() => ({221'--bg-color': isDark.value ? '#1a1a1a' : '#ffffff',222'--text-color': isDark.value ? '#ffffff' : '#1a1a1a',223backgroundColor: 'var(--bg-color)',224color: 'var(--text-color)'225}))226227function toggleTheme() {228isDark.value = !isDark.value229}230</script>231232<style>233.app {234min-height: 100vh;235transition: background-color 0.5s ease, color 0.5s ease;236}237</style>238```239240## Advanced: Numerical Tweening with Watchers241242For smooth number animations (counters, stats), use watchers with animation libraries:243244```vue245<template>246<div>247<input v-model.number="targetNumber" type="number" />248<p class="counter">{{ displayNumber.toFixed(0) }}</p>249</div>250</template>251252<script setup>253import { computed, ref, reactive, watch } from 'vue'254import gsap from 'gsap'255256const targetNumber = ref(0)257const tweened = reactive({ value: 0 })258259// Computed for display260const displayNumber = computed(() => tweened.value)261262watch(targetNumber, (newValue) => {263gsap.to(tweened, {264duration: 0.5,265value: Number(newValue) || 0,266ease: 'power2.out'267})268})269</script>270```271272## Performance Considerations273274```vue275<style>276/* GOOD: GPU-accelerated properties */277.element {278transition: transform 0.3s ease, opacity 0.3s ease;279}280281/* AVOID: Properties that trigger layout recalculation */282.element {283transition: width 0.3s ease, height 0.3s ease, margin 0.3s ease;284}285286/* For high-frequency updates, consider will-change */287.frequently-animated {288will-change: transform;289}290</style>291```292