Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue 3 debugging reference for reactivity issues, computed errors, watcher bugs, async failures, and SSR hydration problems.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/transition-js-hooks-done-callback.md
1---2title: JavaScript Transition Hooks Require done() Callback with css="false"3impact: HIGH4impactDescription: Without calling done(), JavaScript-only transitions complete immediately, skipping the animation entirely5type: gotcha6tags: [vue3, transition, javascript, animation, hooks, gsap, done-callback]7---89# JavaScript Transition Hooks Require done() Callback with css="false"1011**Impact: HIGH** - When using JavaScript-only transitions (with `:css="false"`), the `@enter` and `@leave` hooks **must** call the `done()` callback to signal when the animation completes. Without calling `done()`, Vue considers the transition finished immediately, causing elements to appear/disappear without animation.1213This is especially important when using animation libraries like GSAP, Anime.js, or the Web Animations API.1415## Task Checklist1617- [ ] When using `:css="false"`, always call `done()` in `@enter` and `@leave` hooks18- [ ] Call `done()` when your JavaScript animation completes (in the `onComplete` callback)19- [ ] Remember: `done()` is optional when CSS handles the transition, but **required** with `:css="false"`20- [ ] Use `:css="false"` to prevent CSS rules from interfering with JS animations2122**Problematic Code:**23```vue24<template>25<!-- BAD: No done() callback - animation is skipped! -->26<Transition :css="false" @enter="onEnter" @leave="onLeave">27<div v-if="show" class="box">Content</div>28</Transition>29</template>3031<script setup>32import gsap from 'gsap'3334function onEnter(el) {35// Animation starts but Vue doesn't wait for it!36gsap.from(el, {37opacity: 0,38y: 50,39duration: 0.540})41// Missing done() call - element appears with no animation42}4344function onLeave(el) {45gsap.to(el, {46opacity: 0,47y: -50,48duration: 0.549})50// Missing done() call - element removed immediately!51}52</script>53```5455**Correct Code:**56```vue57<template>58<!-- GOOD: done() callback signals animation completion -->59<Transition :css="false" @enter="onEnter" @leave="onLeave">60<div v-if="show" class="box">Content</div>61</Transition>62</template>6364<script setup>65import gsap from 'gsap'6667function onEnter(el, done) {68gsap.from(el, {69opacity: 0,70y: 50,71duration: 0.5,72onComplete: done // Tell Vue animation is complete73})74}7576function onLeave(el, done) {77gsap.to(el, {78opacity: 0,79y: -50,80duration: 0.5,81onComplete: done // Element removed after animation82})83}84</script>85```8687## Why Use `:css="false"`?88891. **Prevents CSS interference**: Vue won't add transition classes that might conflict902. **Slight performance benefit**: Skips CSS transition detection913. **Clearer intent**: Makes it explicit that JS controls the animation9293```vue94<template>95<!-- Without :css="false", Vue adds v-enter-active etc. classes -->96<!-- These can interfere with your JS animation timing -->97<Transition @enter="onEnter" @leave="onLeave">98<div v-if="show">May have CSS conflicts</div>99</Transition>100101<!-- With :css="false", no classes added - full JS control -->102<Transition :css="false" @enter="onEnter" @leave="onLeave">103<div v-if="show">Pure JS animation</div>104</Transition>105</template>106```107108## Complete JavaScript Transition Example109110```vue111<template>112<Transition113:css="false"114@before-enter="onBeforeEnter"115@enter="onEnter"116@after-enter="onAfterEnter"117@enter-cancelled="onEnterCancelled"118@before-leave="onBeforeLeave"119@leave="onLeave"120@after-leave="onAfterLeave"121@leave-cancelled="onLeaveCancelled"122>123<div v-if="show" class="animated-box">Content</div>124</Transition>125</template>126127<script setup>128import gsap from 'gsap'129import { ref } from 'vue'130131const show = ref(false)132let enterAnimation = null133let leaveAnimation = null134135function onBeforeEnter(el) {136// Set initial state before animation137el.style.opacity = 0138el.style.transform = 'translateY(50px)'139}140141function onEnter(el, done) {142// Store animation reference for potential cancellation143enterAnimation = gsap.to(el, {144opacity: 1,145y: 0,146duration: 0.5,147ease: 'power2.out',148onComplete: done // REQUIRED with :css="false"149})150}151152function onAfterEnter(el) {153// Cleanup after enter completes154enterAnimation = null155}156157function onEnterCancelled() {158// Handle interruption (e.g., user toggles quickly)159if (enterAnimation) {160enterAnimation.kill()161enterAnimation = null162}163}164165function onBeforeLeave(el) {166// Set state before leaving167}168169function onLeave(el, done) {170leaveAnimation = gsap.to(el, {171opacity: 0,172y: -50,173duration: 0.5,174ease: 'power2.in',175onComplete: done // REQUIRED with :css="false"176})177}178179function onAfterLeave(el) {180leaveAnimation = null181}182183function onLeaveCancelled() {184if (leaveAnimation) {185leaveAnimation.kill()186leaveAnimation = null187}188}189</script>190```191192## Using Web Animations API193194```vue195<script setup>196function onEnter(el, done) {197const animation = el.animate([198{ opacity: 0, transform: 'scale(0.9)' },199{ opacity: 1, transform: 'scale(1)' }200], {201duration: 300,202easing: 'ease-out'203})204205animation.onfinish = done // Call done when animation ends206}207208function onLeave(el, done) {209const animation = el.animate([210{ opacity: 1, transform: 'scale(1)' },211{ opacity: 0, transform: 'scale(0.9)' }212], {213duration: 300,214easing: 'ease-in'215})216217animation.onfinish = done218}219</script>220```221222## Common Mistakes223224```javascript225// WRONG: Calling done() immediately instead of after animation226function onEnter(el, done) {227gsap.from(el, { opacity: 0, duration: 0.5 })228done() // Called immediately - animation skipped!229}230231// WRONG: Forgetting done() parameter232function onEnter(el) { // No 'done' parameter233gsap.from(el, {234opacity: 0,235onComplete: done // Error: done is not defined!236})237}238239// CORRECT: Pass done to animation callback240function onEnter(el, done) {241gsap.from(el, {242opacity: 0,243duration: 0.5,244onComplete: done // Called after 0.5s245})246}247```248249## Reference250- [Vue.js Transition - JavaScript Hooks](https://vuejs.org/guide/built-ins/transition.html#javascript-hooks)251- [GSAP with Vue](https://gsap.com/resources/vue/)252