elementui源碼學習仿寫el-collapse示例
引言
本篇文章記錄仿寫一個el-collapse
組件細節,從而有助於大傢更好理解餓瞭麼ui對應組件具體工作細節。
本文是elementui源碼學習仿寫系列的又一篇文章,後續空閑瞭會不斷更新並仿寫其他組件。源碼在github上,大傢可以拉下來,npm start運行跑起來,結合註釋有助於更好的理解。github倉庫地址如下:https://github.com/shuirongsh…
組件思考
el-collapse
即為折疊面板的意思,一般主要是用於:對復雜區域進行分組和隱藏,保持頁面的整潔,有分類整理的意思。
collapse
有折疊的意思,不過fold
也有折疊的意思。所以筆者這裡封裝的組件就改名字瞭,不叫my-collapse
,叫做my-fold
組件的需求
我們先看一下下圖折疊組件的結構圖
結合上圖已經工作經驗,大致分析組件的需求有以下:
- 點擊折疊頭部區域展開或關閉折疊內容體區域
- 展開或折疊的時候,加上過渡效果
- 頭部區域的內容文字參數定義
- 是否隱藏折疊的小箭頭
- 手風琴模式的折疊面板(默認是都可以展開折疊的)
組件實現之父組件統一更改所有子組件狀態
一般情況下父組件更改子組件數據狀態有以下方式:
- 父組件傳遞數據,子組件props接收。更改父組件數據,子組件也就自動更改更新瞭
- 使用
this.$refs.child.xxx = yyy
,給子組件打一個ref
,直接更改對應值即可 - 使用
this.$children
可以訪問所有的子組件實例對象。所以,也可以直接更改,如下:
父組件代碼
// html <template> <div> <h2>下方為三個子組件</h2> <child1 /> <child2 /> <child3 /> <button @click="changeChildData">點擊按鈕更改所有子組件數據</button> </div> </template> // js changeChildData() { // this.$children拿到所有子組件實例對象的數組,遍歷訪問到數據,更改之 this.$children.forEach((child) => { child.flag = !child.flag; }); },
其中一個子組件代碼,另外兩個也一樣
// html <template> <div>child1中的flag--> {{ flag }}</div> </template> // js <script> export default { data() { return { flag: false } }, }; </script>
效果圖
為什麼要提到這個呢?因為手風琴模式下的折疊面板會用到這個方式去更改別的面板,使別的面板關閉
組件實現之高度過渡效果組件的封裝
高度的過渡,主要是從0到某個高度,以及從某個高度到0的變化,需要搭配transition
以及overflow
屬性去控制。我們先看一下簡單的寫法和效果圖,再看一下封裝的組件的代碼
1.簡單寫法
伸手黨福利,復制粘貼即可使用
<!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; /* 以下兩個是必要的樣式控制屬性 */ transition: height 0.2s linear; overflow: hidden; } </style> </head> <body> <button>點擊高度變化</button> <br> <br> <div class="target">過渡的dom</div> <script> let isOpen = true // 初始情況下,標識狀態為打開狀態 let btn = document.querySelector('button') let targetDom = document.querySelector('.target') btn.onclick = () => { // 若為展開狀態,就將其高度置為0,因為css有過渡代碼,所以高度過渡效果就出來瞭 if (isOpen) { targetDom.style.height = 0 + 'px' isOpen = false } // 若為關閉狀態,就將其高度置為原來,因為css有過渡代碼,所以高度過渡效果就出來瞭 else { targetDom.style.height = 120 + 'px' isOpen = true } } </script> </body> </html>
2.簡單寫法效果圖
在我們封裝折疊面板的時候,這個高度變化的過渡組件是必須要有的,沒有的話,折疊面板展開關閉時,會有點突兀,加上一個組件,會絲滑不少。
3.折疊組件的封裝
理解瞭上述的簡單案例,再將其思路應用到組件封裝中去即可
高度組件封裝代碼思路:
根據show
變量的標識,去更改dom.style.height
;
初始加載時,獲取初始高度`dom.offsetHeight更改一次、當show變量標識發生變化的時候,再更改一次。
同時搭配高度的transition樣式控制即可(即:監聽props中show`標識的變化更改之)
封裝好的高度過渡組件代碼如下:
<template> <div class="transitionWrap" ref="transitionWrap"> <slot></slot> </div> </template> <script> export default { props: { // 佈爾值show標識關閉還是展開 show: Boolean, }, data() { return { height: 0, }; }, mounted() { /* dom加載完畢,然後根據標識show去手動更新高度 */ this.$nextTick(() => { this.height = this.$refs.transitionWrap.offsetHeight; this.$refs.transitionWrap.style.height = this.show ? `${this.height}px` : 0; }); // this.$nextTick().then(() => { ... } }, watch: { /* 再監聽標識的變化,從而更改高度,即關閉還是展開 */ show(newVal) { this.$refs.transitionWrap.style.height = newVal ? `${this.height}px` : 0; }, }, }; </script> <style scoped> /* 關鍵css樣式,高度線性勻速過渡 */ .transitionWrap { transition: height 0.2s linear; overflow: hidden; } </style>
另外餓瞭麼UI也提供瞭el-collapse-transition
組件,也是一個不錯的選擇
關於組件中的role屬性和aria-multiselectable等
封裝一套強大的開源組件其實要考慮的東西很多,比如需要適配屏幕閱讀器,我們看一下餓瞭麼UI的el-collapse
組件使用到的兩個屏幕閱讀器屬性role
和aria-multiselectable
。如下圖:
role
屬性是html中語義化標簽的進一步補充(如 屏幕閱讀器,給盲人使用),另舉一個例子<div role="checkbox" aria-checked="checked" />
高度屏幕閱讀器,此處有一個復選框,而且已經被選中瞭aria-multiselectable='true'
告知輔助設備,一次可以展開多個項,還是隻能展開一個
詳情 css http://edu.jb51.net/jqueryui/jqueryui-intro.html
由此可以看出,一套開源的組件,的確是方方面面都要考慮到。
封裝的組件
我們先看一下效果圖
封裝的效果圖
使用自己封裝的折疊組件
<template> <div> <!-- 手風琴模式 --> <my-fold v-model="openArr" accordion @change="changeFn"> <my-fold-item title="第一項" name="one">我是第一項的內容</my-fold-item> <my-fold-item title="第二項" name="two"> <p>我是第二項的內容</p> <p>我是第二項的內容</p> </my-fold-item> <my-fold-item title="第三項" name="three"> <p>我是第三項的內容</p> <p>我是第三項的內容</p> <p>我是第三項的內容</p> </my-fold-item> <my-fold-item title="第四項" name="four"> <p>我是第四項的內容</p> <p>我是第四項的內容</p> <p>我是第四項的內容</p> <p>我是第四項的內容</p> </my-fold-item> </my-fold> <br /> <!-- 可展開多個模式 --> <my-fold v-model="openArr2" @change="changeFn"> <my-fold-item title="第一項" name="one">我是第一項的內容</my-fold-item> <my-fold-item title="第二項" name="two"> <p>我是第二項的內容</p> <p>我是第二項的內容</p> </my-fold-item> <my-fold-item title="第三項" name="three"> <p>我是第三項的內容</p> <p>我是第三項的內容</p> <p>我是第三項的內容</p> </my-fold-item> <my-fold-item title="第四項" name="four"> <p>我是第四項的內容</p> <p>我是第四項的內容</p> <p>我是第四項的內容</p> <p>我是第四項的內容</p> </my-fold-item> </my-fold> </div> </template> <script> export default { data() { return { // 手風琴模式的數組項要麼沒有項,要麼隻能有一個項 openArr: [], // 可展開多個的數組,可以有多個項 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: { // 是否開啟手風琴模式(每次隻能展開一個面板) accordion: { type: Boolean, default: false, // 默認不開啟(可展開多個) }, // 父組件v-model傳參,子組件props中key為'value'接收,'input'事件更改 value: { type: Array, // 手風琴模式的數組項隻能有一個,反之可以有多個 default() { return []; }, }, }, data() { return { // 展開的項可一個,可多個(使用層v-model數組傳的有誰,就展開誰) openArr: this.value, // 收集誰需要展開 }; }, mounted() { // 手動加一個校驗 if (this.accordion & (this.value.length > 1)) { console.error("手風琴模式下,綁定的數組最多一項"); } }, watch: { // 監聽props中value的變化,及時更新 value(value) { this.openArr = value; }, }, methods: { updateVModel(name, isOpen, vNode) { // 若為手風琴模式 if (this.accordion) { // 當某一項打開的時候,才去關閉其他項 isOpen ? this.closeOther(name) : null; this.openArr = [name]; // 手風琴模式隻保留(展開)一個 } // 若為可展開多項模式 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的數據更改 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"> <!-- 頭部部分,主要是點擊時展開內容,以及做小箭頭的旋轉,和頭部的標題呈現 --> <div class="foldItemHeader" @click="handleHeaderClick"> <i v-if="!hiddenArrow" class="el-icon-arrow-right" :class="{ rotate90deg: isOpen }" ></i> {{ title }} </div> <!-- 內容體部分,主要是展開折疊時加上高度過渡效果,這裡封裝瞭一個額外的工具組件 --> <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, // 折疊面板的標題 name: String, // 折疊面板的名字,即為唯一標識符(不可與其他重復!) // 是否隱藏小箭頭,默認false,即展示小箭頭 hiddenArrow: { type: Boolean, default: false, }, }, data() { return { // true為展開即open,false為折疊 // 初始情況下取到父組件myFold組件的展開的數組,看看自身是否在其中 isOpen: this.$parent.openArr.includes(this.name), }; }, methods: { // 點擊展開或折疊 handleHeaderClick() { this.isOpen = !this.isOpen; // 內容依托於變量isOpen直接更新即可 this.$parent.updateVModel(this.name, this.isOpen, this); // 於此同時也要通知父組件去更新 }, }, }; </script> <style lang="less" scoped> .foldItem { width: 100%; height: auto; // 高度由內容區撐開 .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; // 展開折疊項時,小圖標旋轉效果 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>
上述代碼結合註釋,更好的理解哦
以上就是elementui源碼學習仿寫el-collapse示例的詳細內容,更多關於elementui仿寫el-collapse的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 用瞭小米MIX FOLD一個月之後,我已經離不開這塊屏幕瞭
- vue實現多級側邊欄的封裝
- JavaScript–在Vue中使用插槽:slot
- vue實現無縫輪播效果(跑馬燈)
- Vue插槽簡介和使用示例詳解