方法一:使用 Vue 的 <transition>
组件
vue
<template>
<div class="list-container">
<div
v-for="(item, index) in list"
:key="index"
class="list-item"
>
<div class="title-row" @click="toggleExpand(index)">
<h3>{{ item.title }}</h3>
<span class="arrow">
<svg
xmlns="http://www.w3.org/2000/svg"
:style="{ transform: isExpanded[index]? 'rotate(180deg)' : 'rotate(0deg)' }"
width="16"
height="16"
>
<path d="M8 12L3 7 8 2 13 7 8 12Z"/>
</svg>
</span>
</div>
<!-- 使用 transition 组件包裹内容区域 -->
<transition name="slide">
<div class="content" v-if="isExpanded[index]">
<p>{{ item.content }}</p>
</div>
</transition>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const list = ref([
{ title: '列表项1', content: '内容1' },
{ title: '列表项2', content: '内容2' },
{ title: '列表项3', content: '内容3' }
]);
const isExpanded = ref(new Array(list.value.length).fill(false));
const toggleExpand = (index) => {
isExpanded.value[index] = !isExpanded.value[index];
};
</script>
<style scoped>
.list-container {
max-width: 600px;
margin: 0 auto;
}
.list-item {
border: 1px solid #e5e7eb;
border-radius: 8px;
margin-bottom: 16px;
overflow: hidden;
}
.title-row {
display: flex;
padding: 16px;
cursor: pointer;
transition: background-color 0.2s;
}
.title-row:hover {
background-color: #f3f4f6;
}
.arrow {
transition: transform 0.3s ease;
}
/* 内容容器样式 */
.content {
padding: 16px;
background-color: #fff;
line-height: 1.6;
}
/* 定义过渡动画 */
.slide-enter-active,
.slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter-from,
.slide-leave-to {
opacity: 0;
transform: translateY(-10px);
}
</style>
方法二:直接通过 CSS 实现过渡
vue
<template>
<div class="list-container">
<div
v-for="(item, index) in list"
:key="index"
class="list-item"
>
<div class="title-row" @click="toggleExpand(index)">
<h3>{{ item.title }}</h3>
<span class="arrow">
<svg
xmlns="http://www.w3.org/2000/svg"
:style="{ transform: isExpanded[index]? 'rotate(180deg)' : 'rotate(0deg)' }"
width="16"
height="16"
>
<path d="M8 12L3 7 8 2 13 7 8 12Z"/>
</svg>
</span>
</div>
<!-- 添加动态类控制展开状态 -->
<div class="content" v-show="isExpanded[index]" :class="{ expanded: isExpanded[index] }">
<p>{{ item.content }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
const list = ref([
{ title: '列表项1', content: '内容1' },
{ title: '列表项2', content: '内容2' },
{ title: '列表项3', content: '内容3' }
]);
const isExpanded = ref(new Array(list.value.length).fill(false));
const toggleExpand = (index) => {
isExpanded.value[index] = !isExpanded.value[index];
};
</script>
<style scoped>
.list-container {
max-width: 600px;
margin: 0 auto;
}
.list-item {
border: 1px solid #e5e7eb;
border-radius: 8px;
margin-bottom: 16px;
overflow: hidden;
}
.title-row {
display: flex;
padding: 16px;
cursor: pointer;
transition: background-color 0.2s;
}
.title-row:hover {
background-color: #f3f4f6;
}
.arrow {
transition: transform 0.3s ease;
}
/* 内容容器样式 */
.content {
max-height: 0;
padding: 0 16px;
opacity: 0;
overflow: hidden;
transition: all 0.3s ease;
}
/* 展开状态样式 */
.content.expanded {
max-height: 500px;
padding: 16px;
opacity: 1;
}
</style>
关键改进点
效果说明
- 展开时:内容从上方平滑滑入,同时渐显
- 收起时:内容平滑滑出并渐隐
- 箭头图标也会平滑旋转,与内容动画保持一致