預覽地址 這篇部落格意在總結記錄Bug的解決和完成元件的過程。
Tabs標籤頁
<x-tabs :selected.sync="selected">
<x-tabs-item item="經濟" name="1">
財經報導
</x-tabs-item>
</x-tabs>
複製程式碼
點選變換
按照上面的結構,內容應該放在tabs-item
,那麼選擇既
就由tabs
控制。
tabs
大致HTML結構
<div class="tabs">
<div class="tabs-header"........>
<divclass="tabs-header-item".........>
// 標籤頁,這裡簡稱 header-item
//.........
</div>
<div ref="line" class="line" v-if="!cards"></div>
//高亮線條
</div>
<div class="tabs-content">
<slot></slot>
//展示內容
</div>
</div>
複製程式碼
由於只有兩個元件,所以元件通訊就非常簡單了。這裡不做多的說明
內容切換的動畫效果
如同預覽所見的(目前所有position只有一種動畫效果),有一種輪播切換的趕腳。
遇到的問題
一般這種動畫容易造成一些bug,例如
- 新內容進來和舊內容出去在同一時間,會存在擠兌。官網這裡由明確的一些解決方案。像是
動態元件
,key
或者mode='xxx'
什麼的。但是slot
這種相當麻煩,在不知道插入元素的情況下,很難做到動畫的順暢和沒有額外的bug。 - 使用
鉤子函式
都設定為絕對定位,結束改回來。但動畫期間父元素失去高度。會有抖動的不美觀效果。
在這裡的解決方法。
首先我去Ant Design看了看。 因為這個動畫效果本來就是模仿他的。
大概猜了一下,Ant Design的做法應該是把三組並排,通過控制父級的負margin
控制顯示。
只需在移出的時候絕對定位一下就行了
position: absolute;
left: 0;
top: 0;
複製程式碼
然後無非就是一個從x(左/右)移入,一個反向移出就行了。
line的位移
line
的位移應該是這部分相對比較麻煩的。之所以選擇用div
單獨一個元素來做高亮線條,而不是header-item
的border
。是因為
- 更容易變換位置,適合後面的
position
變換 - 可以調整高度和寬度
大概的css
首先父元素肯定要相對定位
.tabs-header{
display: flex;
//....
position: relative;
height: $tab-height;
//....
}
複製程式碼
子元素絕對定位
>.line{
position: absolute;
bottom: 0;
left: 0;
//....
}
複製程式碼
根據不同的position
,line
完成不同方向和位置的偏移
這樣子會寫相當多的重複程式碼,非常不利於閱讀和維護,例如。
程式碼重構:表驅動程式設計
先做命名上的修改
const {line} = this.$refs
let [left,top] = [item.offsetLeft,item.offsetTop]
let {width,height} = item.getBoundingClientRect()
let positionName
複製程式碼
這裡面看到其實每個位置的設定都只需要三個屬性
,而且top
和bottom
,left
和right
都是一樣的設定。
所以
//linemove函式,這名字真的不行,後面改改。
let position = {
topOrBottom:{
width:`${width}px`,
height:0,
transform: `translate(${left}px,0)`
},
leftOrRight:{
width: 0,
height:`${height}px`,
transform: `translateY(${top}px)`
}
}
positionName = this.position === 'left' || this.position === 'right' ? 'leftOrRight' : 'topOrBottom';
line.style.height = position[positionName].height
line.style.width = position[positionName].width
line.style.transform = position[positionName].transform
複製程式碼
這樣子的修改就使程式碼可讀性更強,一目瞭然。
line
位置不正確的問題
其實本來是對的,展示元件的時候,position
繫結動態資料,在button
切換的時候,發現line
位置不正確。
watch:{
position(){
this.lineMove()
}
},
複製程式碼
這裡簡單講一下我對$nextTick
的理解:
return function queueNextTick (cb?: Function, ctx?: Object){
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
//.......
}
} else if (_resolve) {
//... 這裡是傳入的回撥為空且支援【promise】的時候預設選擇的【promise】,這在官網裡面有講過
}
})
}
複製程式碼
先對傳入的回撥函式
做一個收集,放進一個callbacks
陣列裡面
事實上$nextTick
也希望非同步任務可以儘快一點執行。所以在api
的選擇上是
- 第一步先選擇屬於
‘微任務’
的promise
,看看瀏覽器是否原生支援promise
- 不支援則降級為
macroTimerFunc
- 同樣在
macroTimerFunc
里根據瀏覽器的支援程度對這些非同步api
做一個選擇(setImmediate
,messagechannel
....),反正setTimeout
是最低的。
然後會在下一個tick
執行(flushCallbacks
)對callbacks
做個從頭開始的遍歷執行(就像是佇列一樣),這裡具體的比較細。反正最終就是依次執行傳入的nextTick
就對了。
這樣子看來,在我測試這裡的情況裡應該是選擇的promise
,這當然是不行的。
因為動畫過渡的影響,在執行的時候,動畫也在執行,這時dom
上元素的css屬性並不是我想要的。
現在的選擇就是
- 移除展示的動畫過渡效果,其實就是展示元件,位置改變的動畫沒了,其他正常使用的過渡效果依然存在。
- 用我現在水平能想到的
setTimeout
,雖然不知道後面會不會有Bug
最後的程式碼
watch:{
position(){
setTimeout(()=>{
this.lineMove()
},300)
}
},
mounted(){
this.$nextTick(()=>{
this.lineMove()
//......
})
//.....
}
複製程式碼
後面會繼續仿著element和Antd的元件效果,實現關於標籤頁更多功能。 0
部落格裡面如果有不正確或者錯誤的地方,希望大佬們可以批評指正。 如果你覺得將就還行,給我的輪子專案一個star就是最大的鼓勵了。