elementui源碼學(xué)習(xí)仿寫el-collapse示例
引言
本篇文章記錄仿寫一個(gè)el-collapse
組件細(xì)節(jié),從而有助于大家更好理解餓了么ui對(duì)應(yīng)組件具體工作細(xì)節(jié)。
本文是elementui源碼學(xué)習(xí)仿寫系列的又一篇文章,后續(xù)空閑了會(huì)不斷更新并仿寫其他組件。源碼在github上,大家可以拉下來,npm start運(yùn)行跑起來,結(jié)合注釋有助于更好的理解。github倉(cāng)庫地址如下:https://github.com/shuirongsh...
組件思考
el-collapse
即為折疊面板的意思,一般主要是用于:對(duì)復(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
,直接更改對(duì)應(yīng)值即可 - 使用
this.$children
可以訪問所有的子組件實(shí)例對(duì)象。所以,也可以直接更改,如下:
父組件代碼
// html <template> <div> <h2>下方為三個(gè)子組件</h2> <child1 /> <child2 /> <child3 /> <button @click="changeChildData">點(diǎn)擊按鈕更改所有子組件數(shù)據(jù)</button> </div> </template> // js changeChildData() { // this.$children拿到所有子組件實(shí)例對(duì)象的數(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)琴模式下的折疊面板會(huì)用到這個(gè)方式去更改別的面板,使別的面板關(guān)閉
組件實(shí)現(xiàn)之高度過渡效果組件的封裝
高度的過渡,主要是從0到某個(gè)高度,以及從某個(gè)高度到0的變化,需要搭配transition
以及overflow
屬性去控制。我們先看一下簡(jiǎn)單的寫法和效果圖,再看一下封裝的組件的代碼
1.簡(jiǎn)單寫法
伸手黨福利,復(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)識(shí)狀態(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.簡(jiǎn)單寫法效果圖
在我們封裝折疊面板的時(shí)候,這個(gè)高度變化的過渡組件是必須要有的,沒有的話,折疊面板展開關(guān)閉時(shí),會(huì)有點(diǎn)突兀,加上一個(gè)組件,會(huì)絲滑不少。
3.折疊組件的封裝
理解了上述的簡(jiǎn)單案例,再將其思路應(yīng)用到組件封裝中去即可
高度組件封裝代碼思路:
根據(jù)show
變量的標(biāo)識(shí),去更改dom.style.height
;
初始加載時(shí),獲取初始高度`dom.offsetHeight更改一次、當(dāng)show變量標(biāo)識(shí)發(fā)生變化的時(shí)候,再更改一次。
同時(shí)搭配高度的transition樣式控制即可(即:監(jiān)聽props中show`標(biāo)識(shí)的變化更改之)
封裝好的高度過渡組件代碼如下:
<template> <div class="transitionWrap" ref="transitionWrap"> <slot></slot> </div> </template> <script> export default { props: { // 布爾值show標(biāo)識(shí)關(guān)閉還是展開 show: Boolean, }, data() { return { height: 0, }; }, mounted() { /* dom加載完畢,然后根據(jù)標(biāo)識(shí)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)識(shí)的變化,從而更改高度,即關(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)識(shí)符(不可與其他重復(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的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue動(dòng)態(tài)樣式方法實(shí)例總結(jié)
在vue項(xiàng)目中,很多場(chǎng)景要求我們動(dòng)態(tài)改變?cè)氐臉邮?下面這篇文章主要給大家介紹了關(guān)于Vue動(dòng)態(tài)樣式方法的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02對(duì)Vue.js之事件的綁定(v-on: 或者 @ )詳解
今天小編就為大家分享一篇對(duì)Vue.js之事件的綁定(v-on: 或者 @ )詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09vue-openlayers實(shí)現(xiàn)地圖坐標(biāo)彈框效果
這篇文章主要為大家詳細(xì)介紹了vue-openlayers實(shí)現(xiàn)地圖坐標(biāo)彈框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09Vue3自動(dòng)引入組件與組件庫的方法實(shí)例
關(guān)于vue?組件還是非常好用的,真正掌握預(yù)計(jì)需要一段時(shí)間,下面這篇文章主要給大家介紹了關(guān)于Vue3自動(dòng)引入組件與組件庫的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10element-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-03Element Table的row-class-name無效與動(dòng)態(tài)高亮顯示選中行背景色
這篇文章主要介紹了Element Table的row-class-name無效與動(dòng)態(tài)高亮顯示選中行背景色,本文詳細(xì)的介紹了解決方案,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11vue項(xiàng)目中使用bpmn為節(jié)點(diǎn)添加顏色的方法
這篇文章主要介紹了vue項(xiàng)目中使用bpmn為節(jié)點(diǎn)添加顏色的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04