Animating State with Watchers

+15 Mana ✨

Animate value changes using watchers and tweening libraries.

Number Tweening

vue
<script setup>
import { ref, watch } from 'vue'

const actualNumber = ref(0)
const displayNumber = ref(0)

watch(actualNumber, (newValue) => {
  const startValue = displayNumber.value
  const difference = newValue - startValue
  const duration = 500
  const startTime = performance.now()
  
  function animate(currentTime: number) {
    const elapsed = currentTime - startTime
    const progress = Math.min(elapsed / duration, 1)
    
    // Easing function (ease-out)
    const eased = 1 - Math.pow(1 - progress, 3)
    
    displayNumber.value = Math.round(startValue + difference * eased)
    
    if (progress < 1) {
      requestAnimationFrame(animate)
    }
  }
  
  requestAnimationFrame(animate)
})

function increment() {
  actualNumber.value += 100
}
</script>

<template>
  <button @click="increment">Add 100</button>
  <p>{{ displayNumber }}</p>
</template>

Using GSAP for Tweening

vue
<script setup>
import { ref, reactive, watch } from 'vue'
import gsap from 'gsap'

const number = ref(0)
const tweened = reactive({ value: 0 })

watch(number, (newValue) => {
  gsap.to(tweened, {
    value: newValue,
    duration: 0.5,
    ease: 'power2.out'
  })
})
</script>

<template>
  <input type="number" v-model.number="number" />
  <p>{{ tweened.value.toFixed(0) }}</p>
</template>

Color Transitions

vue
<script setup>
import { ref, computed, watch } from 'vue'
import gsap from 'gsap'

const colors = ['#ff0000', '#00ff00', '#0000ff', '#ffff00']
const colorIndex = ref(0)
const tweenedColor = ref(colors[0])

const currentColor = computed(() => colors[colorIndex.value])

watch(currentColor, (newColor) => {
  gsap.to(tweenedColor, {
    value: newColor,
    duration: 0.5
  })
})

function nextColor() {
  colorIndex.value = (colorIndex.value + 1) % colors.length
}
</script>

<template>
  <div 
    class="color-box" 
    :style="{ backgroundColor: tweenedColor }"
    @click="nextColor"
  >
    Click me
  </div>
</template>

Progress Bar Animation

vue
<script setup>
import { ref, watch } from 'vue'

const progress = ref(0)
const animatedProgress = ref(0)

watch(progress, (newValue) => {
  const start = animatedProgress.value
  const change = newValue - start
  const duration = 600
  const startTime = performance.now()
  
  function animate(time: number) {
    const elapsed = time - startTime
    const t = Math.min(elapsed / duration, 1)
    
    // Smooth easing
    const eased = t < 0.5 
      ? 4 * t * t * t 
      : 1 - Math.pow(-2 * t + 2, 3) / 2
    
    animatedProgress.value = start + change * eased
    
    if (t < 1) requestAnimationFrame(animate)
  }
  
  requestAnimationFrame(animate)
})
</script>

<template>
  <div class="progress-bar">
    <div 
      class="progress-fill"
      :style="{ width: animatedProgress + '%' }"
    />
  </div>
  <input type="range" v-model.number="progress" min="0" max="100" />
</template>

<style>
.progress-bar {
  width: 100%;
  height: 20px;
  background: #eee;
  border-radius: 10px;
  overflow: hidden;
}

.progress-fill {
  height: 100%;
  background: linear-gradient(90deg, #42b883, #35495e);
  transition: none; /* JavaScript handles animation */
}
</style>

Chart Data Animation

vue
<script setup>
import { ref, reactive, watch } from 'vue'
import gsap from 'gsap'

const data = ref([25, 50, 75, 100, 60])
const tweenedData = reactive([25, 50, 75, 100, 60])

watch(data, (newData) => {
  newData.forEach((value, index) => {
    gsap.to(tweenedData, {
      [index]: value,
      duration: 0.5,
      ease: 'power2.out'
    })
  })
}, { deep: true })

function randomize() {
  data.value = data.value.map(() => Math.floor(Math.random() * 100))
}
</script>

<template>
  <button @click="randomize">Randomize</button>
  
  <div class="chart">
    <div 
      v-for="(value, index) in tweenedData"
      :key="index"
      class="bar"
      :style="{ height: value + '%' }"
    />
  </div>
</template>

<style>
.chart {
  display: flex;
  align-items: flex-end;
  height: 200px;
  gap: 10px;
}

.bar {
  flex: 1;
  background: #42b883;
  border-radius: 4px 4px 0 0;
}
</style>
✓ Completed