过渡动画 #

概述 #

Alpine.js 提供了内置的过渡动画系统,可以轻松地为元素添加进入和离开动画。支持 CSS 过渡、CSS 动画和 JavaScript 钩子。

x-transition 基本用法 #

默认过渡 #

html
<div x-data="{ show: false }">
    <button @click="show = !show">切换</button>
    <div x-show="show" x-transition>
        带过渡动画的内容
    </div>
</div>

默认过渡使用 CSS 的 opacity 和 transform 属性。

过渡方向 #

html
<div x-data="{ show: false }">
    <button @click="show = !show">切换</button>
    
    <div x-show="show" x-transition.opacity>透明度过渡</div>
    <div x-show="show" x-transition.scale>缩放过渡</div>
    <div x-show="show" x-transition.scale.opacity>缩放+透明度过渡</div>
</div>

自定义 CSS 过渡 #

使用 Tailwind CSS #

html
<div x-data="{ show: false }">
    <button @click="show = !show">切换</button>
    <div 
        x-show="show"
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0 transform scale-90"
        x-transition:enter-end="opacity-100 transform scale-100"
        x-transition:leave="transition ease-in duration-200"
        x-transition:leave-start="opacity-100 transform scale-100"
        x-transition:leave-end="opacity-0 transform scale-90"
    >
        自定义过渡效果
    </div>
</div>

使用自定义 CSS #

html
<style>
.fade-enter {
    opacity: 0;
    transform: translateY(-10px);
}
.fade-enter-active {
    transition: opacity 0.3s, transform 0.3s;
}
.fade-leave {
    opacity: 1;
    transform: translateY(0);
}
.fade-leave-active {
    transition: opacity 0.3s, transform 0.3s;
    opacity: 0;
    transform: translateY(-10px);
}
</style>

<div x-data="{ show: false }">
    <button @click="show = !show">切换</button>
    <div 
        x-show="show"
        x-transition:enter="fade-enter"
        x-transition:enter-active="fade-enter-active"
        x-transition:leave="fade-leave"
        x-transition:leave-active="fade-leave-active"
    >
        内容
    </div>
</div>

过渡类名 #

类名说明 #

类名 说明
x-transition:enter 进入过渡开始时添加
x-transition:enter-start 进入过渡开始状态
x-transition:enter-end 进入过渡结束状态
x-transition:leave 离开过渡开始时添加
x-transition:leave-start 离开过渡开始状态
x-transition:leave-end 离开过渡结束状态

过渡流程 #

text
进入:
1. 添加 enter 和 enter-start 类
2. 下一帧移除 enter-start,添加 enter-end
3. 过渡完成后移除所有类

离开:
1. 添加 leave 和 leave-start 类
2. 下一帧移除 leave-start,添加 leave-end
3. 过渡完成后移除所有类并隐藏元素

实用示例 #

模态框动画 #

html
<div x-data="{ open: false }">
    <button @click="open = true">打开模态框</button>
    
    <div 
        x-show="open"
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0"
        x-transition:enter-end="opacity-100"
        x-transition:leave="transition ease-in duration-200"
        x-transition:leave-start="opacity-100"
        x-transition:leave-end="opacity-0"
        class="fixed inset-0 bg-black/50 flex items-center justify-center"
        @click.self="open = false"
    >
        <div 
            x-show="open"
            x-transition:enter="transition ease-out duration-300"
            x-transition:enter-start="opacity-0 transform scale-95"
            x-transition:enter-end="opacity-100 transform scale-100"
            x-transition:leave="transition ease-in duration-200"
            x-transition:leave-start="opacity-100 transform scale-100"
            x-transition:leave-end="opacity-0 transform scale-95"
            class="bg-white rounded-lg p-6 max-w-md"
            @click.stop
        >
            <h2 class="text-xl font-bold mb-4">模态框标题</h2>
            <p class="mb-4">模态框内容</p>
            <button @click="open = false" class="btn">关闭</button>
        </div>
    </div>
</div>

下拉菜单动画 #

html
<div x-data="{ open: false }" class="relative">
    <button @click="open = !open" class="btn">
        菜单
        <span x-show="!open">▼</span>
        <span x-show="open">▲</span>
    </button>
    
    <div 
        x-show="open"
        @click.away="open = false"
        x-transition:enter="transition ease-out duration-100"
        x-transition:enter-start="opacity-0 transform scale-95"
        x-transition:enter-end="opacity-100 transform scale-100"
        x-transition:leave="transition ease-in duration-75"
        x-transition:leave-start="opacity-100 transform scale-100"
        x-transition:leave-end="opacity-0 transform scale-95"
        class="absolute mt-2 bg-white rounded-md shadow-lg"
    >
        <a href="#" class="block px-4 py-2 hover:bg-gray-100">选项 1</a>
        <a href="#" class="block px-4 py-2 hover:bg-gray-100">选项 2</a>
        <a href="#" class="block px-4 py-2 hover:bg-gray-100">选项 3</a>
    </div>
</div>

通知动画 #

html
<div x-data="{
    notifications: [],
    add(message) {
        const id = Date.now()
        this.notifications.push({ id, message })
        setTimeout(() => this.remove(id), 3000)
    },
    remove(id) {
        this.notifications = this.notifications.filter(n => n.id !== id)
    }
}">
    <button @click="add('新消息 ' + Date.now())">添加通知</button>
    
    <div class="fixed top-4 right-4 space-y-2">
        <template x-for="notification in notifications" :key="notification.id">
            <div 
                x-transition:enter="transition ease-out duration-300"
                x-transition:enter-start="opacity-0 transform translate-x-full"
                x-transition:enter-end="opacity-100 transform translate-x-0"
                x-transition:leave="transition ease-in duration-200"
                x-transition:leave-start="opacity-100 transform translate-x-0"
                x-transition:leave-end="opacity-0 transform translate-x-full"
                class="bg-blue-500 text-white px-4 py-2 rounded shadow"
                x-text="notification.message"
            ></div>
        </template>
    </div>
</div>

手风琴动画 #

html
<div x-data="{
    items: [
        { title: '标题 1', content: '内容 1', open: false },
        { title: '标题 2', content: '内容 2', open: false },
        { title: '标题 3', content: '内容 3', open: false }
    ]
}">
    <template x-for="item in items">
        <div class="border rounded mb-2">
            <button 
                @click="item.open = !item.open"
                class="w-full px-4 py-2 text-left font-medium"
            >
                <span x-text="item.title"></span>
                <span x-show="!item.open">+</span>
                <span x-show="item.open">-</span>
            </button>
            <div 
                x-show="item.open"
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0 max-h-0"
                x-transition:enter-end="opacity-100 max-h-40"
                x-transition:leave="transition ease-in duration-200"
                x-transition:leave-start="opacity-100 max-h-40"
                x-transition:leave-end="opacity-0 max-h-0"
                class="overflow-hidden px-4"
            >
                <p class="py-2" x-text="item.content"></p>
            </div>
        </div>
    </template>
</div>

列表动画 #

html
<div x-data="{
    items: ['Item 1', 'Item 2', 'Item 3'],
    addItem() {
        this.items.push('Item ' + (this.items.length + 1))
    },
    removeItem(index) {
        this.items.splice(index, 1)
    }
}">
    <button @click="addItem()">添加</button>
    
    <ul class="space-y-2">
        <template x-for="(item, index) in items" :key="item">
            <li 
                x-transition:enter="transition ease-out duration-200"
                x-transition:enter-start="opacity-0 transform -translate-y-2"
                x-transition:enter-end="opacity-100 transform translate-y-0"
                x-transition:leave="transition ease-in duration-200"
                x-transition:leave-start="opacity-100 transform translate-y-0"
                x-transition:leave-end="opacity-0 transform -translate-y-2"
                class="flex justify-between items-center p-2 bg-gray-100 rounded"
            >
                <span x-text="item"></span>
                <button @click="removeItem(index)" class="text-red-500">删除</button>
            </li>
        </template>
    </ul>
</div>

JavaScript 钩子 #

使用钩子函数 #

html
<div x-data="{ show: false }">
    <button @click="show = !show">切换</button>
    <div 
        x-show="show"
        x-transition:enter="enterHandler"
        x-transition:leave="leaveHandler"
    >
        内容
    </div>
</div>

<script>
function enterHandler(el, done) {
    el.style.opacity = 0
    el.style.transform = 'translateY(-20px)'
    
    requestAnimationFrame(() => {
        el.style.transition = 'all 0.3s ease'
        el.style.opacity = 1
        el.style.transform = 'translateY(0)'
    })
    
    el.addEventListener('transitionend', done, { once: true })
}

function leaveHandler(el, done) {
    el.style.transition = 'all 0.3s ease'
    el.style.opacity = 0
    el.style.transform = 'translateY(-20px)'
    
    el.addEventListener('transitionend', done, { once: true })
}
</script>

在组件中定义钩子 #

html
<div x-data="{
    show: false,
    beforeEnter(el) {
        el.style.opacity = 0
    },
    enter(el, done) {
        Velocity(el, { opacity: 1 }, { duration: 300, complete: done })
    },
    leave(el, done) {
        Velocity(el, { opacity: 0 }, { duration: 300, complete: done })
    }
}">
    <button @click="show = !show">切换</button>
    <div 
        x-show="show"
        x-transition:enter="enter"
        x-transition:leave="leave"
    >
        内容
    </div>
</div>

x-if 与过渡 #

x-if 也支持过渡动画:

html
<div x-data="{ show: false }">
    <button @click="show = !show">切换</button>
    <template x-if="show">
        <div x-transition>
            带过渡的内容
        </div>
    </template>
</div>

注意事项 #

1. 过渡类冲突 #

确保过渡类不会与现有样式冲突:

html
<div x-show="show" x-transition class="my-class">
</div>

2. 性能考虑 #

使用 transformopacity 进行动画,避免触发重排:

html
<div 
    x-transition:enter-start="opacity-0 transform scale-95"
    x-transition:enter-end="opacity-100 transform scale-100"
>
</div>

3. 过渡时间 #

确保 CSS 过渡时间与预期一致:

css
.transition-300 {
    transition: all 0.3s ease;
}

小结 #

过渡动画要点:

  • 使用 x-transition 添加过渡效果
  • 支持自定义 CSS 类
  • 支持 JavaScript 钩子
  • x-showx-if 都支持过渡
  • 使用 transform 和 opacity 优化性能

下一章,我们将学习插件开发。

最后更新:2026-03-28