Vuejs高度的改變動畫探索:折疊面板Collapse組件的優(yōu)秀實現(xiàn)方案
使用過CSS transition屬性的童鞋們應該都清楚,當元素在過渡開始或者結束時的高度為auto時,動畫是不生效的;那么如何才能實現(xiàn)元素高度的改變動畫效果呢?本篇文章將為大家提供一個基于Vue3的非常簡潔的解決方案。
要實現(xiàn)高度的改變動畫,我們需要在動畫進行之前為元素設置準確的高度。
當元素由可見變?yōu)殡[藏狀態(tài)時,我們需要先獲取元素的計算高度,將其設置到style屬性上,然后執(zhí)行一個觸發(fā)瀏覽器渲染引擎重繪的動作,然后再將高度設置為0,這樣高度的改變動畫就會正常進行。
當元素由隱藏變?yōu)榭梢姞顟B(tài)時,我們需要先將高度設置為auto,然后獲取元素的計算高度,再將高度設置為0,然后執(zhí)行一個觸發(fā)瀏覽器渲染引擎重繪的動作,然后再將高度設置為計算高度,這樣高度的改變動畫就會正常進行。
現(xiàn)在,根據(jù)以上實現(xiàn)原理分析,我們創(chuàng)建一個高度的改變動畫通用組件CollapseTransition.vue。該組件非常簡單,僅需30多行代碼。我?guī)缀趺啃写a都有注釋,大家應該能看懂吧?
<template>
<transition v-bind="listeners">
<!-- 當visible的值發(fā)生改變時,過渡組件的監(jiān)聽器就會觸發(fā) -->
<div v-show="visible" class="x-collapse-transition">
<slot />
</div>
</transition>
</template>
<script setup>
defineProps({ visible: Boolean })
const listeners = {
// 元素由隱藏變?yōu)榭梢?br> onEnter (/** @type {HTMLElement} */ el) {
el.style.height = 'auto' // 將高度設為auto,是為了獲取該元素的計算高度
const endHeight = window.getComputedStyle(el).height // 計算高度
el.style.height = '0px' // 將高度再設置為0
el.offsetHeight // 強制重繪,重繪后再改變高度才會產生動畫
el.style.height = endHeight // 設置為計算高度
},
onAfterEnter (/** @type {HTMLElement} */ el) {
el.style.height = null // 過渡進入之后,將高度恢復為null
},
// 元素由可見變?yōu)殡[藏
onLeave (/** @type {HTMLElement} */ el) {
el.style.height = window.getComputedStyle(el).height // 計算高度
el.offsetHeight // 強制重繪,重繪后再改變高度才會產生動畫
el.style.height = '0px' // 將高度設置為0
},
onAfterLeave (/** @type {HTMLElement} */ el) {
el.style.height = null // 過渡離開之后,將高度恢復為null
}
}
</script>
<style lang="scss">
.x-collapse-transition {
overflow: hidden;
transition: height .22s ease-in-out;
}
</style>
以上就是實現(xiàn)高度的改變動畫的通用組件源碼,童鞋們理解了嗎?是不是非常簡單?現(xiàn)在,我們實現(xiàn)折疊面板組件。使用過element-ui這樣的UI庫的童鞋們應該都知道,折疊面板是由兩個組件折疊面板Collapse和折疊面板項CollapseItem組合而成;
現(xiàn)在,我們先實現(xiàn)CollapseItem.vue組件。為了節(jié)省篇幅,我將源碼中的空行全部去掉了,縮進比較規(guī)范,自認為可讀性還行;源碼如下,一共30多行,我直接在源碼中添加了注釋,就不過多解釋了。
<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' // 當它是個字符串常量就行
import { N, S, B } from '../../types' // N: Number, S: String, B: Boolean
import { genKey } from '../../utils' // 生成唯一性的數(shù)值key,之前的文章中有源碼
import XCollapseTransition from './CollapseTransition.vue' // 折疊動畫組件
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()) // 如果沒提供name,我們生成一個key
const isActive = computed(() => collapse.includes(idKey.value)) // 是否展開狀態(tài)
function onToggle () { // 內容可見時隱藏,隱藏時可見
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)在,我們實現(xiàn)Collapse.vue組件。該組件仍然只有30多行,大家理解起來應該很輕松,我就直接在源碼里添加注釋作為講解了。
<template>
<div class="x-collapse">
<slot />
</div>
</template>
<script setup>
import { provide, reactive, ref, watch } from 'vue'
import { COLLAPSE_INJECT_KEY } from '../../constants' // 一個字符串常量
import { S, B, A } from '../../types' // 參看CollapseItem組件
const props = defineProps({
modelValue: [S, A], // Vue3使用modelValue取代了Vue2中的value
accordion: B // 是否手風琴模式,手風琴模式只能有1個面板項是展開狀態(tài)
})
const emit = defineEmits(['update:modelValue', 'change'])
function emitValue (v) {
emit('update:modelValue', v) // 與props.modelValue結合實現(xiàn)雙向數(shù)據(jù)綁定
emit('change', v)
}
const model = ref(props.modelValue)
watch(() => props.modelValue, v => model.value = v)
provide(COLLAPSE_INJECT_KEY, { // 提供2個方法用于注入子組件,給子組件調用
includes (v) { // 根據(jù)面板的key,判斷是否包含該面板項
return props.accordion ? model.value === v : (model.value || []).includes(v)
},
updateModel (v) { // 更新面板項的內容折疊和展開狀態(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>
以上就是折疊面板組件的實現(xiàn)。包括折疊動畫組件,一共僅需不到150行代碼,是不是非常簡單?