elementui源碼學(xué)習(xí)仿寫el-collapse示例
引言
本篇文章記錄仿寫一個(gè)el-collapse組件細(xì)節(jié),從而有助于大家更好理解餓了么ui對應(yīng)組件具體工作細(xì)節(jié)。
本文是elementui源碼學(xué)習(xí)仿寫系列的又一篇文章,后續(xù)空閑了會不斷更新并仿寫其他組件。源碼在github上,大家可以拉下來,npm start運(yùn)行跑起來,結(jié)合注釋有助于更好的理解。github倉庫地址如下:https://github.com/shuirongsh...
組件思考
el-collapse即為折疊面板的意思,一般主要是用于:對復(fù)雜區(qū)域進(jìn)行分組和隱藏,保持頁面的整潔,有分類整理的意思。
collapse有折疊的意思,不過fold也有折疊的意思。所以筆者這里封裝的組件就改名字了,不叫my-collapse,叫做my-fold
組件的需求
我們先看一下下圖折疊組件的結(jié)構(gòu)圖

結(jié)合上圖已經(jīng)工作經(jīng)驗(yàn),大致分析組件的需求有以下:
- 點(diǎn)擊折疊頭部區(qū)域展開或關(guān)閉折疊內(nèi)容體區(qū)域
- 展開或折疊的時(shí)候,加上過渡效果
- 頭部區(qū)域的內(nèi)容文字參數(shù)定義
- 是否隱藏折疊的小箭頭
- 手風(fēng)琴模式的折疊面板(默認(rèn)是都可以展開折疊的)
組件實(shí)現(xiàn)之父組件統(tǒng)一更改所有子組件狀態(tài)
一般情況下父組件更改子組件數(shù)據(jù)狀態(tài)有以下方式:
- 父組件傳遞數(shù)據(jù),子組件props接收。更改父組件數(shù)據(jù),子組件也就自動(dòng)更改更新了
- 使用
this.$refs.child.xxx = yyy,給子組件打一個(gè)ref,直接更改對應(yīng)值即可 - 使用
this.$children可以訪問所有的子組件實(shí)例對象。所以,也可以直接更改,如下:
父組件代碼
// html
<template>
<div>
<h2>下方為三個(gè)子組件</h2>
<child1 />
<child2 />
<child3 />
<button @click="changeChildData">點(diǎn)擊按鈕更改所有子組件數(shù)據(jù)</button>
</div>
</template>
// js
changeChildData() {
// this.$children拿到所有子組件實(shí)例對象的數(shù)組,遍歷訪問到數(shù)據(jù),更改之
this.$children.forEach((child) => {
child.flag = !child.flag;
});
},其中一個(gè)子組件代碼,另外兩個(gè)也一樣
// html
<template>
<div>child1中的flag--> {{ flag }}</div>
</template>
// js
<script>
export default {
data() { return { flag: false } },
};
</script>效果圖

為什么要提到這個(gè)呢?因?yàn)槭诛L(fēng)琴模式下的折疊面板會用到這個(gè)方式去更改別的面板,使別的面板關(guān)閉
組件實(shí)現(xiàn)之高度過渡效果組件的封裝
高度的過渡,主要是從0到某個(gè)高度,以及從某個(gè)高度到0的變化,需要搭配transition以及overflow屬性去控制。我們先看一下簡單的寫法和效果圖,再看一下封裝的組件的代碼
1.簡單寫法
伸手黨福利,復(fù)制粘貼即可使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.target {
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
background-color: #baf;
/* 以下兩個(gè)是必要的樣式控制屬性 */
transition: height 0.2s linear;
overflow: hidden;
}
</style>
</head>
<body>
<button>點(diǎn)擊高度變化</button>
<br>
<br>
<div class="target">過渡的dom</div>
<script>
let isOpen = true // 初始情況下,標(biāo)識狀態(tài)為打開狀態(tài)
let btn = document.querySelector('button')
let targetDom = document.querySelector('.target')
btn.onclick = () => {
// 若為展開狀態(tài),就將其高度置為0,因?yàn)閏ss有過渡代碼,所以高度過渡效果就出來了
if (isOpen) {
targetDom.style.height = 0 + 'px'
isOpen = false
}
// 若為關(guān)閉狀態(tài),就將其高度置為原來,因?yàn)閏ss有過渡代碼,所以高度過渡效果就出來了
else {
targetDom.style.height = 120 + 'px'
isOpen = true
}
}
</script>
</body>
</html>2.簡單寫法效果圖

在我們封裝折疊面板的時(shí)候,這個(gè)高度變化的過渡組件是必須要有的,沒有的話,折疊面板展開關(guān)閉時(shí),會有點(diǎn)突兀,加上一個(gè)組件,會絲滑不少。
3.折疊組件的封裝
理解了上述的簡單案例,再將其思路應(yīng)用到組件封裝中去即可
高度組件封裝代碼思路:
根據(jù)show變量的標(biāo)識,去更改dom.style.height;
初始加載時(shí),獲取初始高度`dom.offsetHeight更改一次、當(dāng)show變量標(biāo)識發(fā)生變化的時(shí)候,再更改一次。
同時(shí)搭配高度的transition樣式控制即可(即:監(jiān)聽props中show`標(biāo)識的變化更改之)
封裝好的高度過渡組件代碼如下:
<template>
<div class="transitionWrap" ref="transitionWrap">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
// 布爾值show標(biāo)識關(guān)閉還是展開
show: Boolean,
},
data() {
return {
height: 0,
};
},
mounted() {
/* dom加載完畢,然后根據(jù)標(biāo)識show去手動(dòng)更新高度 */
this.$nextTick(() => {
this.height = this.$refs.transitionWrap.offsetHeight;
this.$refs.transitionWrap.style.height = this.show
? `${this.height}px`
: 0;
});
// this.$nextTick().then(() => { ... }
},
watch: {
/* 再監(jiān)聽標(biāo)識的變化,從而更改高度,即關(guān)閉還是展開 */
show(newVal) {
this.$refs.transitionWrap.style.height = newVal ? `${this.height}px` : 0;
},
},
};
</script>
<style scoped>
/* 關(guān)鍵css樣式,高度線性勻速過渡 */
.transitionWrap {
transition: height 0.2s linear;
overflow: hidden;
}
</style> 另外餓了么UI也提供了el-collapse-transition組件,也是一個(gè)不錯(cuò)的選擇
關(guān)于組件中的role屬性和aria-multiselectable等
封裝一套強(qiáng)大的開源組件其實(shí)要考慮的東西很多,比如需要適配屏幕閱讀器,我們看一下餓了么UI的el-collapse組件使用到的兩個(gè)屏幕閱讀器屬性role和aria-multiselectable。如下圖:

role屬性是html中語義化標(biāo)簽的進(jìn)一步補(bǔ)充(如 屏幕閱讀器,給盲人使用),另舉一個(gè)例子<div role="checkbox" aria-checked="checked" />高度屏幕閱讀器,此處有一個(gè)復(fù)選框,而且已經(jīng)被選中了aria-multiselectable='true'告知輔助設(shè)備,一次可以展開多個(gè)項(xiàng),還是只能展開一個(gè)
詳情 css http://edu.jb51.net/jqueryui/jqueryui-intro.html
由此可以看出,一套開源的組件,的確是方方面面都要考慮到。
封裝的組件
我們先看一下效果圖
封裝的效果圖

使用自己封裝的折疊組件
<template>
<div>
<!-- 手風(fēng)琴模式 -->
<my-fold v-model="openArr" accordion @change="changeFn">
<my-fold-item title="第一項(xiàng)" name="one">我是第一項(xiàng)的內(nèi)容</my-fold-item>
<my-fold-item title="第二項(xiàng)" name="two">
<p>我是第二項(xiàng)的內(nèi)容</p>
<p>我是第二項(xiàng)的內(nèi)容</p>
</my-fold-item>
<my-fold-item title="第三項(xiàng)" name="three">
<p>我是第三項(xiàng)的內(nèi)容</p>
<p>我是第三項(xiàng)的內(nèi)容</p>
<p>我是第三項(xiàng)的內(nèi)容</p>
</my-fold-item>
<my-fold-item title="第四項(xiàng)" name="four">
<p>我是第四項(xiàng)的內(nèi)容</p>
<p>我是第四項(xiàng)的內(nèi)容</p>
<p>我是第四項(xiàng)的內(nèi)容</p>
<p>我是第四項(xiàng)的內(nèi)容</p>
</my-fold-item>
</my-fold>
<br />
<!-- 可展開多個(gè)模式 -->
<my-fold v-model="openArr2" @change="changeFn">
<my-fold-item title="第一項(xiàng)" name="one">我是第一項(xiàng)的內(nèi)容</my-fold-item>
<my-fold-item title="第二項(xiàng)" name="two">
<p>我是第二項(xiàng)的內(nèi)容</p>
<p>我是第二項(xiàng)的內(nèi)容</p>
</my-fold-item>
<my-fold-item title="第三項(xiàng)" name="three">
<p>我是第三項(xiàng)的內(nèi)容</p>
<p>我是第三項(xiàng)的內(nèi)容</p>
<p>我是第三項(xiàng)的內(nèi)容</p>
</my-fold-item>
<my-fold-item title="第四項(xiàng)" name="four">
<p>我是第四項(xiàng)的內(nèi)容</p>
<p>我是第四項(xiàng)的內(nèi)容</p>
<p>我是第四項(xiàng)的內(nèi)容</p>
<p>我是第四項(xiàng)的內(nèi)容</p>
</my-fold-item>
</my-fold>
</div>
</template>
<script>
export default {
data() {
return {
// 手風(fēng)琴模式的數(shù)組項(xiàng)要么沒有項(xiàng),要么只能有一個(gè)項(xiàng)
openArr: [],
// 可展開多個(gè)的數(shù)組,可以有多個(gè)項(xiàng)
openArr2: ["one", "two"],
};
},
methods: {
changeFn(name, isOpen, vNode) {
console.log(name, isOpen, vNode);
},
},
};
</script>my-fold組件
<template>
<div class="myFoldWrap">
<slot></slot>
</div>
</template>
<script>
export default {
name: "myFold",
props: {
// 是否開啟手風(fēng)琴模式(每次只能展開一個(gè)面板)
accordion: {
type: Boolean,
default: false, // 默認(rèn)不開啟(可展開多個(gè))
},
// 父組件v-model傳參,子組件props中key為'value'接收,'input'事件更改
value: {
type: Array, // 手風(fēng)琴模式的數(shù)組項(xiàng)只能有一個(gè),反之可以有多個(gè)
default() {
return [];
},
},
},
data() {
return {
// 展開的項(xiàng)可一個(gè),可多個(gè)(使用層v-model數(shù)組傳的有誰,就展開誰)
openArr: this.value, // 收集誰需要展開
};
},
mounted() {
// 手動(dòng)加一個(gè)校驗(yàn)
if (this.accordion & (this.value.length > 1)) {
console.error("手風(fēng)琴模式下,綁定的數(shù)組最多一項(xiàng)");
}
},
watch: {
// 監(jiān)聽props中value的變化,及時(shí)更新
value(value) {
this.openArr = value;
},
},
methods: {
updateVModel(name, isOpen, vNode) {
// 若為手風(fēng)琴模式
if (this.accordion) {
// 當(dāng)某一項(xiàng)打開的時(shí)候,才去關(guān)閉其他項(xiàng)
isOpen ? this.closeOther(name) : null;
this.openArr = [name]; // 手風(fēng)琴模式只保留(展開)一個(gè)
}
// 若為可展開多項(xiàng)模式
else {
let i = this.openArr.indexOf(name);
// 包含就刪掉、不包含就追加
i > -1 ? this.openArr.splice(i, 1) : this.openArr.push(name);
}
// 無論那種模式,都需要更改并通知外層使用組件
this.$emit("input", this.openArr); // input事件控制v-model的數(shù)據(jù)更改
this.$emit("change", name, isOpen, vNode); // change事件拋出去,供用戶使用
},
closeOther(name) {
this.$children.forEach((item) => {
// 將除了自身以外的都置為false,故其他的就都折疊上了
if (item.name != name) {
item.isOpen = false;
}
});
},
},
};
</script>
<style lang="less" scoped>
.myFoldWrap {
border: 1px solid #e9e9e9;
}
</style>my-fold-item組件
<template>
<div class="foldItem">
<!-- 頭部部分,主要是點(diǎn)擊時(shí)展開內(nèi)容,以及做小箭頭的旋轉(zhuǎn),和頭部的標(biāo)題呈現(xiàn) -->
<div class="foldItemHeader" @click="handleHeaderClick">
<i
v-if="!hiddenArrow"
class="el-icon-arrow-right"
:class="{ rotate90deg: isOpen }"
></i>
{{ title }}
</div>
<!-- 內(nèi)容體部分,主要是展開折疊時(shí)加上高度過渡效果,這里封裝了一個(gè)額外的工具組件 -->
<transition-height class="transitionHeight" :show="isOpen">
<div class="foldItemBody">
<slot></slot>
</div>
</transition-height>
</div>
</template>
<script>
import transitionHeight from "@/components/myUtils/transitionHeight/index.vue";
export default {
name: "myFoldItem",
components: {
transitionHeight, // 高度過渡組件
},
props: {
title: String, // 折疊面板的標(biāo)題
name: String, // 折疊面板的名字,即為唯一標(biāo)識符(不可與其他重復(fù)?。?
// 是否隱藏小箭頭,默認(rèn)false,即展示小箭頭
hiddenArrow: {
type: Boolean,
default: false,
},
},
data() {
return {
// true為展開即open,false為折疊
// 初始情況下取到父組件myFold組件的展開的數(shù)組,看看自身是否在其中
isOpen: this.$parent.openArr.includes(this.name),
};
},
methods: {
// 點(diǎn)擊展開或折疊
handleHeaderClick() {
this.isOpen = !this.isOpen; // 內(nèi)容依托于變量isOpen直接更新即可
this.$parent.updateVModel(this.name, this.isOpen, this); // 于此同時(shí)也要通知父組件去更新
},
},
};
</script>
<style lang="less" scoped>
.foldItem {
width: 100%;
height: auto; // 高度由內(nèi)容區(qū)撐開
.foldItemHeader {
box-sizing: border-box;
padding-left: 8px;
min-height: 50px;
display: flex;
align-items: center;
background-color: #fafafa;
cursor: pointer;
border-bottom: 1px solid #e9e9e9;
// 展開折疊項(xiàng)時(shí),小圖標(biāo)旋轉(zhuǎn)效果
i {
transform: rotate(0deg);
transition: all 0.24s;
margin-right: 8px;
}
.rotate90deg {
transform: rotate(90deg);
transition: all 0.24s;
}
}
.foldItemBody {
width: 100%;
height: auto;
box-sizing: border-box;
padding: 12px 12px 12px 8px;
border-bottom: 1px solid #e9e9e9;
}
}
// 去除和父組件的邊框重疊
.foldItem:last-child .foldItemHeader {
border-bottom: none !important;
}
.foldItem:last-child .transitionHeight .foldItemBody {
border-top: 1px solid #e9e9e9;
border-bottom: none !important;
}
</style>上述代碼結(jié)合注釋,更好的理解哦
以上就是elementui源碼學(xué)習(xí)仿寫el-collapse示例的詳細(xì)內(nèi)容,更多關(guān)于elementui仿寫el-collapse的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue動(dòng)態(tài)樣式方法實(shí)例總結(jié)
在vue項(xiàng)目中,很多場景要求我們動(dòng)態(tài)改變元素的樣式,下面這篇文章主要給大家介紹了關(guān)于Vue動(dòng)態(tài)樣式方法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
vue-openlayers實(shí)現(xiàn)地圖坐標(biāo)彈框效果
這篇文章主要為大家詳細(xì)介紹了vue-openlayers實(shí)現(xiàn)地圖坐標(biāo)彈框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09
Vue3自動(dòng)引入組件與組件庫的方法實(shí)例
關(guān)于vue?組件還是非常好用的,真正掌握預(yù)計(jì)需要一段時(shí)間,下面這篇文章主要給大家介紹了關(guān)于Vue3自動(dòng)引入組件與組件庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
element-plus+Vue3實(shí)現(xiàn)表格數(shù)據(jù)動(dòng)態(tài)渲染
在Vue中,el-table是element-ui提供的強(qiáng)大表格組件,可以用于展示靜態(tài)和動(dòng)態(tài)表格數(shù)據(jù),本文主要介紹了element-plus+Vue3實(shí)現(xiàn)表格數(shù)據(jù)動(dòng)態(tài)渲染,感興趣的可以了解一下2024-03-03
Element Table的row-class-name無效與動(dòng)態(tài)高亮顯示選中行背景色
這篇文章主要介紹了Element Table的row-class-name無效與動(dòng)態(tài)高亮顯示選中行背景色,本文詳細(xì)的介紹了解決方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11
vue項(xiàng)目中使用bpmn為節(jié)點(diǎn)添加顏色的方法
這篇文章主要介紹了vue項(xiàng)目中使用bpmn為節(jié)點(diǎn)添加顏色的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04

