vue元件之路之Tabs標籤頁

火貓裸輝耀發表於2019-03-28

預覽地址 這篇部落格意在總結記錄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>
複製程式碼

vue元件之路之Tabs標籤頁

由於只有兩個元件,所以元件通訊就非常簡單了。這裡不做多的說明

內容切換的動畫效果

如同預覽所見的(目前所有position只有一種動畫效果),有一種輪播切換的趕腳。

遇到的問題

一般這種動畫容易造成一些bug,例如

  • 新內容進來和舊內容出去在同一時間,會存在擠兌。官網這裡由明確的一些解決方案。像是動態元件key或者mode='xxx'什麼的。但是slot這種相當麻煩,在不知道插入元素的情況下,很難做到動畫的順暢和沒有額外的bug。
  • 使用鉤子函式都設定為絕對定位,結束改回來。但動畫期間父元素失去高度。會有抖動的不美觀效果。

在這裡的解決方法。

首先我去Ant Design看了看。 因為這個動畫效果本來就是模仿他的。

大概猜了一下,Ant Design的做法應該是把三組並排,通過控制父級的負margin控制顯示。

vue元件之路之Tabs標籤頁
大佬的方法還是牛批,但這裡我不想多寫程式碼就直接借鑑(chao xi)以前的輪播

只需在移出的時候絕對定位一下就行了

 position: absolute;
 left: 0;
 top: 0;
複製程式碼

然後無非就是一個從x(左/右)移入,一個反向移出就行了。

line的位移

line的位移應該是這部分相對比較麻煩的。之所以選擇用div單獨一個元素來做高亮線條,而不是header-itemborder。是因為

  • 更容易變換位置,適合後面的position變換
  • 可以調整高度寬度

大概的css

首先父元素肯定要相對定位

 .tabs-header{
            display: flex;
            //....
            position: relative;
            height: $tab-height;
            //....
 }
複製程式碼

子元素絕對定位

 >.line{
        position: absolute;
        bottom: 0;
        left: 0;
       //....
        }
複製程式碼

根據不同的positionline完成不同方向和位置的偏移

vue元件之路之Tabs標籤頁

這樣子會寫相當多的重複程式碼,非常不利於閱讀和維護,例如。

vue元件之路之Tabs標籤頁

程式碼重構:表驅動程式設計

先做命名上的修改

        const {line} = this.$refs
        let [left,top] = [item.offsetLeft,item.offsetTop]
        let {width,height} = item.getBoundingClientRect()
        let positionName
複製程式碼

這裡面看到其實每個位置的設定都只需要三個屬性,而且topbottomleftright都是一樣的設定。 所以


           //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()
               //......
            })
           //.....
        }
複製程式碼

後面會繼續仿著elementAntd的元件效果,實現關於標籤頁更多功能。 0

部落格裡面如果有不正確或者錯誤的地方,希望大佬們可以批評指正。 如果你覺得將就還行,給我的輪子專案一個star就是最大的鼓勵了。

相關文章