Vuejs高度的改變動(dòng)畫(huà)探索:折疊面板Collapse組件的優(yōu)秀實(shí)現(xiàn)方案
使用過(guò)CSS transition屬性的童鞋們應(yīng)該都清楚,當(dāng)元素在過(guò)渡開(kāi)始或者結(jié)束時(shí)的高度為auto時(shí),動(dòng)畫(huà)是不生效的;那么如何才能實(shí)現(xiàn)元素高度的改變動(dòng)畫(huà)效果呢?本篇文章將為大家提供一個(gè)基于Vue3的非常簡(jiǎn)潔的解決方案。
要實(shí)現(xiàn)高度的改變動(dòng)畫(huà),我們需要在動(dòng)畫(huà)進(jìn)行之前為元素設(shè)置準(zhǔn)確的高度。
當(dāng)元素由可見(jiàn)變?yōu)殡[藏狀態(tài)時(shí),我們需要先獲取元素的計(jì)算高度,將其設(shè)置到style屬性上,然后執(zhí)行一個(gè)觸發(fā)瀏覽器渲染引擎重繪的動(dòng)作,然后再將高度設(shè)置為0,這樣高度的改變動(dòng)畫(huà)就會(huì)正常進(jìn)行。
當(dāng)元素由隱藏變?yōu)榭梢?jiàn)狀態(tài)時(shí),我們需要先將高度設(shè)置為auto,然后獲取元素的計(jì)算高度,再將高度設(shè)置為0,然后執(zhí)行一個(gè)觸發(fā)瀏覽器渲染引擎重繪的動(dòng)作,然后再將高度設(shè)置為計(jì)算高度,這樣高度的改變動(dòng)畫(huà)就會(huì)正常進(jìn)行。
現(xiàn)在,根據(jù)以上實(shí)現(xiàn)原理分析,我們創(chuàng)建一個(gè)高度的改變動(dòng)畫(huà)通用組件CollapseTransition.vue。該組件非常簡(jiǎn)單,僅需30多行代碼。我?guī)缀趺啃写a都有注釋?zhuān)蠹覒?yīng)該能看懂吧?
<template>
<transition v-bind="listeners">
<!-- 當(dāng)visible的值發(fā)生改變時(shí),過(guò)渡組件的監(jiān)聽(tīng)器就會(huì)觸發(fā) -->
<div v-show="visible" class="x-collapse-transition">
<slot />
</div>
</transition>
</template>
<script setup>
defineProps({ visible: Boolean })
const listeners = {
// 元素由隱藏變?yōu)榭梢?jiàn)
onEnter (/** @type {HTMLElement} */ el) {
el.style.height = 'auto' // 將高度設(shè)為auto,是為了獲取該元素的計(jì)算高度
const endHeight = window.getComputedStyle(el).height // 計(jì)算高度
el.style.height = '0px' // 將高度再設(shè)置為0
el.offsetHeight // 強(qiáng)制重繪,重繪后再改變高度才會(huì)產(chǎn)生動(dòng)畫(huà)
el.style.height = endHeight // 設(shè)置為計(jì)算高度
},
onAfterEnter (/** @type {HTMLElement} */ el) {
el.style.height = null // 過(guò)渡進(jìn)入之后,將高度恢復(fù)為null
},
// 元素由可見(jiàn)變?yōu)殡[藏
onLeave (/** @type {HTMLElement} */ el) {
el.style.height = window.getComputedStyle(el).height // 計(jì)算高度
el.offsetHeight // 強(qiáng)制重繪,重繪后再改變高度才會(huì)產(chǎn)生動(dòng)畫(huà)
el.style.height = '0px' // 將高度設(shè)置為0
},
onAfterLeave (/** @type {HTMLElement} */ el) {
el.style.height = null // 過(guò)渡離開(kāi)之后,將高度恢復(fù)為null
}
}
</script>
<style lang="scss">
.x-collapse-transition {
overflow: hidden;
transition: height .22s ease-in-out;
}
</style>
以上就是實(shí)現(xiàn)高度的改變動(dòng)畫(huà)的通用組件源碼,童鞋們理解了嗎?是不是非常簡(jiǎn)單?現(xiàn)在,我們實(shí)現(xiàn)折疊面板組件。使用過(guò)element-ui這樣的UI庫(kù)的童鞋們應(yīng)該都知道,折疊面板是由兩個(gè)組件折疊面板Collapse和折疊面板項(xiàng)CollapseItem組合而成;
現(xiàn)在,我們先實(shí)現(xiàn)CollapseItem.vue組件。為了節(jié)省篇幅,我將源碼中的空行全部去掉了,縮進(jìn)比較規(guī)范,自認(rèn)為可讀性還行;源碼如下,一共30多行,我直接在源碼中添加了注釋?zhuān)筒贿^(guò)多解釋了。
<template>
<div :class="[cls, { 'is-active': isActive }]">
<div :class="`${cls}_header`" @click="onToggle">
<div :class="`${cls}_title`">
<slot name="title">{{ title }}</slot>
</div>
<i :class="['x-icon-arrow-right', `${cls}_arrow`, { 'is-active': isActive }]"></i>
</div>
<x-collapse-transition :visible="isActive">
<div :class="`${cls}_content`">
<slot />
</div>
</x-collapse-transition>
</div>
</template>
<script setup>
import { computed, inject } from 'vue'
import { COLLAPSE_INJECT_KEY } from '../../constants' // 當(dāng)它是個(gè)字符串常量就行
import { N, S, B } from '../../types' // N: Number, S: String, B: Boolean
import { genKey } from '../../utils' // 生成唯一性的數(shù)值key,之前的文章中有源碼
import XCollapseTransition from './CollapseTransition.vue' // 折疊動(dòng)畫(huà)組件
const props = defineProps({
name: [S, N],
title: S,
disabled: B
})
const collapse = inject(COLLAPSE_INJECT_KEY, '') // 注入折疊面板組件提供的一些屬性和方法
const cls = 'x-collapse-item'
const idKey = computed(() => props.name || genKey()) // 如果沒(méi)提供name,我們生成一個(gè)key
const isActive = computed(() => collapse.includes(idKey.value)) // 是否展開(kāi)狀態(tài)
function onToggle () { // 內(nèi)容可見(jiàn)時(shí)隱藏,隱藏時(shí)可見(jiàn)
collapse.updateModel(idKey.value)
}
</script>
這是CollapseItem.vue組件的樣式。
@import './common/var.scss';
.x-collapse-item {
font-size: 13px;
background-color: #fff;
color: $--color-text-primary;
border-bottom: 1px solid $--border-color-lighter;
&:first-child {
border-top: 1px solid $--border-color-lighter;
}
&_header {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
cursor: pointer;
}
&_content {
line-height: 1.8;
padding-bottom: 25px;
}
&_arrow {
margin-right: 8px;
transition: transform .2s;
&.is-active {
transform: rotate(90deg);
}
}
}
現(xiàn)在,我們實(shí)現(xiàn)Collapse.vue組件。該組件仍然只有30多行,大家理解起來(lái)應(yīng)該很輕松,我就直接在源碼里添加注釋作為講解了。
<template>
<div class="x-collapse">
<slot />
</div>
</template>
<script setup>
import { provide, reactive, ref, watch } from 'vue'
import { COLLAPSE_INJECT_KEY } from '../../constants' // 一個(gè)字符串常量
import { S, B, A } from '../../types' // 參看CollapseItem組件
const props = defineProps({
modelValue: [S, A], // Vue3使用modelValue取代了Vue2中的value
accordion: B // 是否手風(fēng)琴模式,手風(fēng)琴模式只能有1個(gè)面板項(xiàng)是展開(kāi)狀態(tài)
})
const emit = defineEmits(['update:modelValue', 'change'])
function emitValue (v) {
emit('update:modelValue', v) // 與props.modelValue結(jié)合實(shí)現(xiàn)雙向數(shù)據(jù)綁定
emit('change', v)
}
const model = ref(props.modelValue)
watch(() => props.modelValue, v => model.value = v)
provide(COLLAPSE_INJECT_KEY, { // 提供2個(gè)方法用于注入子組件,給子組件調(diào)用
includes (v) { // 根據(jù)面板的key,判斷是否包含該面板項(xiàng)
return props.accordion ? model.value === v : (model.value || []).includes(v)
},
updateModel (v) { // 更新面板項(xiàng)的內(nèi)容折疊和展開(kāi)狀態(tài)
const { value } = model
if (props.accordion) {
model.value = value === v ? null : v
emitValue(model.value)
} else {
if (!value) model.value = []
const index = model.value.indexOf(v)
index > -1 ? model.value.splice(index, 1) : model.value.push(v)
emitValue(model.value)
}
}
})
</script>
以上就是折疊面板組件的實(shí)現(xiàn)。包括折疊動(dòng)畫(huà)組件,一共僅需不到150行代碼,是不是非常簡(jiǎn)單?