自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Vue實現內部組件輪播切換效果

開發(fā) 開發(fā)工具
我們可以引入一個輪播組件,但是有個問題,通常輪播組件都會把所有的slide都渲染出來再進行切換,這樣就導致所有的資源都會觸發(fā)加載,這可能不是我們所期待的,畢竟如果slide比較多的情況需要一次性加載的圖片等資源太多了。

[[226134]]

對于那些不需要路由的內部組件,在切換的時候希望增加一個輪播過渡的效果,效果如下:

我們可以引入一個輪播組件,但是有個問題,通常輪播組件都會把所有的slide都渲染出來再進行切換,這樣就導致所有的資源都會觸發(fā)加載,這可能不是我們所期待的,畢竟如果slide比較多的情況需要一次性加載的圖片等資源太多了。所以我們可以手動簡單地寫一個,滿足需求即可。

現在一步步來實現這個功能,先寫一個實現基本切換的demo.

1. 實現切換

先用vue-cli搭建一個工程腳手架,使用以下命令:

  1. npm install -g vue-cli 
  2. vue init webpack slide-demo # 運行后router等都選擇no 

這樣就搭了一個webpack + vue的工程,進入slide-demo目錄,查看src/App.vue,這個文件是初始化工具提供的,是整個頁面的組件。還有一個src/components目錄,這個是放子組件的目錄。

在這個目錄里面新建3個組件:task-1.vue、task-2.vue、task-3.vue,然后在App.vue里面import進來,如下App.vue所示:

  1. <script> 
  2. // import HelloWorld from './components/HelloWorld' 
  3. import Task1 from "./components/task-1"
  4. import Task2 from "./components/task-2"
  5. import Task3 from "./components/task-3"
  6.   
  7. export default { 
  8.     name'App'
  9.     components: { 
  10.         Task1, 
  11.         Task2, 
  12.         Task3 
  13.     } 
  14. </script> 

我們的數據格式questions是這樣的:

  1. [{index: 1, type: 1, content: ''}, {index: 2, type: 1, content: ''},  
  2.  {index: 3, type: 2, content: ''}, {index: 4, type: 3, content: ''}] 

它是一個數組,數組里的每個元素代表每道題,每道題都有一個類型,如選擇題、填空題、判斷題等,分別對應上面的task-1、task-2、task-3,我們用一個currentIndex變量表示當前是在哪道題,初始化為0,如下代碼所示(添加到App.vue里面):

  1. data() { 
  2.      return { 
  3.          currentIndex: 0 
  4.      }; 
  5.  }, 
  6.  created() { 
  7.      // 請求question數據 
  8.      this.questions = [ 
  9.          {index: 1, type: 1, question: ''}, /*...*/]; 
  10.  }, 

通過改變currentIndex的值,從而切到一下題即下一個組件,要怎么實現這個切換的效果呢?

可以使用Vue自定義的一個全局組件component,給合它的is屬性,達到動態(tài)改變組件的目的,如下代碼所示:

  1. <template> 
  2. <div id="app"
  3.     <div class="task-container"
  4.         <component :is="'task-' + questions[currentIndex].type"  
  5.         </component> 
  6.     </div> 
  7. </div> 
  8. </template> 

當currentIndex增加時,就會改變:is里面的值,依次從task-1變到task-2、task-3等,這樣component就會換成相應的task組件。

接著,再添加一個切換到下一題的按鈕,在這個按鈕的響應函數里面改變currentIndex的值。同時把question的數據傳給component:

  1. <template> 
  2. <div id="app"
  3.     <div class="task-container"
  4.         <component :is="'task-' + questions[currentIndex].type"  
  5.             :question="questions[currentIndex]"></component> 
  6.         <button class="next-question" @click="nextQuestion">下一題</button> 
  7.     </div> 
  8. </div> 
  9. </template> 

響應函數nextQuestion實現如下:

  1. methods: { 
  2.     nextQuestion() { 
  3.         this.currentIndex = (this.currentIndex + 1)  
  4.                % this.questions.length; 
  5.     } 
  6. }, 

具體每個task的實現參考如task-1.vue示例:

  1. <template> 
  2. <section
  3.     <h2>{{question.index}}. 選擇題</h2> 
  4.     <div>{{content}}</div> 
  5. </section
  6. </template> 
  7. <script> 
  8. export default { 
  9.     props: ["question"
  10. </script> 

***的效果如下(加上題目內容):

2. 添加輪播切換效果

輪播切換通常是把所有的slide都拼起來,拼成一張長長的橫圖,然后改變這個橫圖在顯示容器里面的位置,如老牌jQuery插件flipsnap.js,它是把所有的slide都float: left,形成一張長圖,然后改變這張長圖的translate值,達到切換的目的。這個插件的缺點是沒有辦法從***一張切回***張,解決這個問題的方法之一是不斷地移動DOM:每次切的時候都把***張移到***一張的后面,這樣就實現了***一張點下一張的時候回到***張的目的,但是這樣移來移去地對性能消耗比較大,不是很優(yōu)雅。另外一個輪播插件jssor slider,它也是把所有的slide都渲染出來,然后每次切換的時候都動態(tài)地計算每張slide的translate的值,而不是整體長圖的位置,這樣就不用移動DOM節(jié)點,相對較為優(yōu)雅。還有很多Vue的輪播插件的實現也是類似上面提到的方式。

不管怎么樣,上面的輪播模式都不太適用于我們的場景,其中一個是這種答題的場景不需要切回上一題,每道題做完就不能回去了,更重要的一個是我們不希望一次性把所有的slide都渲染出來,這樣會導致每張幻燈片里的資源都觸發(fā)加載,就比如img標簽雖然你把它display: none了,但是只要它的src是一個正常的url,它就會請求加載。 由于slide往往會比較多,就不使用這種輪播插件了。

還可以使用Vue自帶的transition,但是transition的問題是,切下一個的時候,上一個不見了,因為被銷毀了,只有下一個的動畫,并且不能預加載下一個slide的資源。

所以我們手動實現一個。

我的想法是每次都準備兩個slide,第1個slide是當前展示用的,第2個slide拼在它的后面,準備切過來,當第2個slide切過來之后,刪掉第1個slide,然后在第2個的后面再接第3個slide,不斷地重復這個過程。如果我們沒有使用Vue,而是自己增刪DOM,那么沒什么問題,可以很任性地自己發(fā)揮。使用Vue可以怎么優(yōu)雅地實現這個功能呢?

在上面一個component的基礎上,再添加一個component,剛開始第1個component是當前展示的,而第2個component是拼在它右邊的,當第2個切過去之后,就把第1個移到第2的后面,同時把內容改成第3個slide的內容,依此類推。使用Vue不太好動態(tài)地改DOM,但是可以借助jssor slider的思想,不移動DOM,只是改變component的translate的值。

給其中一個component套一個next-task的類,具有這個類的組件就表示它是下一張要出現的,它需要translateX(100%),如下代碼所示:

  1. <template> 
  2. <div id="app"
  3.     <div class="task-container"
  4.         <component :is="'task-' + questions[currentIndex].type"  
  5.             ></component> 
  6.         <component :is="'task-' + questions[currentIndex + 1].type"  
  7.             class="next-task"></component> 
  8.     </div> 
  9. </div> 
  10. </template> 
  11.   
  12. <style> 
  13. .next-task { 
  14.     display: none; 
  15.     transform: translateX(100%); 
  16.     /* 添加一個動畫,當改變transform的值時就會觸發(fā)這個動畫 */ 
  17.     transition: transform 0.5s ease; 
  18. </style> 

上面代碼把具有.next-task類的component隱藏了,這樣是做個優(yōu)化,因為display: none的元素只會構建DOM,不會進行l(wèi)ayout和render渲染。

所以就把問題轉換成怎么在這兩個component之間,切換next-task的類。一開始next-task是在第2個,當第2個切過來之后,next-task變成加在第1個上面,這樣輪流交替。

進而,發(fā)現一個規(guī)律,如果currentIndex是偶數話,如o、2、4…,那么next-task是加在第2個component的,而如果currentIndex是奇數,則next-task是加在第1個component的。所以可以根據currentIndex的奇偶性切換。

如下代碼所示:

  1. <template> 
  2. <div id="app"
  3.     <div class="task-container"
  4.         <component :is="'task-' + questions[evenIndex].type"  
  5.             :class="{'next-task': nextIndex === evenIndex}" 
  6.             ref="evenTask"></component> 
  7.         <component :is="'task-' + questions[oddIndex].type"  
  8.             :class="{'next-task': nextIndex === oddIndex} 
  9.             ref="oddTask"></component> 
  10.     </div> 
  11. </div> 
  12. </template> 
  13.   
  14. <script> 
  15.   
  16. export default { 
  17.     name'App'
  18.     data() { 
  19.         return { 
  20.             currentIndex: 0, // 當前顯示的index 
  21.             nextIndex: 1,    // 表示下一張index,值為currentIndex + 1 
  22.             evenIndex: 0,    // 偶數的index,值為currentIndex或者是currentIndex + 1 
  23.             oddIndex: 1      // 奇數的index,值同上 
  24.         }; 
  25.     }, 

第1個component用來顯示偶數的slide,第2個是用來顯示奇數的slide(分別用一個evenIndex和oddIndex代表),如果nextIndex是偶數的,那么偶數的component就會有一個next-task的類,反之則亦然。然后在下一題按鈕的響應函數里面改變這幾個index的值:

  1. methods: { 
  2.     nextQuestion() { 
  3.         this.currentIndex = (this.currentIndex + 1)  
  4.             % this.questions.length; 
  5.         this._slideToNext(); 
  6.     }, 
  7.     // 切到下一步的動畫效果 
  8.     _slideToNext() { 
  9.   
  10.     } 

nextQuestion函數可能還有其它一些處理,在它里面調一下_slideToNext函數,這個函數的實現如下:

  1. _slideToNext() { 
  2.     // 當前slide的類型(currentIndex已經加1了,這里要反一下) 
  3.     let currentType = this.currentIndex % 2 ? "even" : "odd"
  4.         // 下一個slide的類型 
  5.         nextType =  this.currentIndex % 2 ? "odd""even"
  6.     // 獲取下一個slide的dom元素 
  7.     let $nextSlide = this.$refs[`${nextType}Task`].$el; 
  8.     $nextSlide.style.display = "block"
  9.     // 把下一個slide的translate值置為0,原本是translateX(100%) 
  10.     $nextSlide.style.transform = "translateX(0)"
  11.     // 等動畫結束后更新數據 
  12.     setTimeout(() => { 
  13.         this.nextIndex = (this.currentIndex + 1)  
  14.             % this.questions.length; 
  15.         // 原本的next是當前顯示的slide 
  16.         this[`${nextType}Index`] = this.currentIndex; 
  17.         // 而原本的current slide要顯示下下張的內容了 
  18.         this[`${currentType}Index`] = this.nextIndex; 
  19.     }, 500); 

代碼把下一個slide的display改成block,并把它的translateX的值置為0,這個時候不能馬上更新數據觸發(fā)DOM更新,要等到下一個slide移過去的動畫結束之后再開始操作,所以加了一個setTimeout,在回調里面調換nextTask的類,加到原本的current slide,并把它的內容置成下下張的內容。這些都是通過改變相應的index完成的。

這樣基本上就完成了,但是我們發(fā)現一個問題,切是切過去了,就是沒有動畫效果。這個是因為從display: none變到display: block是沒有動畫的,要么改成visibility: hidden到visible,要么觸發(fā)動畫的操作加到$nextTick或者setTimeout 0里面,考慮到性能問題,這里使用第二種方案:

  1. $nextSlide.style.display = "block"
  2. // 這里使用setimeout,因為$nextTick有時候沒有動畫,非必現 
  3. setTimeout(() => { 
  4.     $nextSlide.style.transform = "translateX(0)"
  5.     // ... 
  6. }, 0); 

經過這樣的處理之后,點下一題就有動畫了,但是又發(fā)現一個問題,就是偶數的next-task會被蓋住,因為偶數的是使用***個component,它是會被第二個compoent蓋住的,所以需要給它加一個z-index:

  1. .next-task {  
  2.     display: none; 
  3.     transform: translateX(100%); 
  4.     transition: transform 0.5s ease; 
  5.     z-index: 2; 

這個問題還比較好處理,另外一個不太好處理的問題是:動畫的時間是0.5s,如果用戶點下一題的速度很快在0.5s之內,上面的代碼執(zhí)行就會有問題,會導致數據錯亂。如果每次切到下一題之后按鈕初始化都是disabled,因為當前題還沒答,只有答了才能變成可點狀態(tài),可以保證0.5s的時間是夠的,那么就可以不用考慮這種情況。但是如果需要處理這種情況呢?

3. 解決點擊過快的問題

我想到兩個方法,***個方法是用一個sliding的變量標志當前是否是在進行切換的動畫,如果是的話,點擊按鈕的時候就直接更新數據,同時把setTimeout 0.5s的計時器清掉。這個方法可以解決數據錯亂的問題,但是切換的效果沒有了,或者是切換到一半的時候突然就沒了,這樣體驗不是很好。

第二個方法是延后切換,即如果用戶點擊過快的時候,把這些操作排隊,等著一個個做切換的動畫。

我們用一個數組表示隊列,如果當前已經在做滑動的動畫,則入隊暫不執(zhí)行動畫,如下代碼所示:

  1. methods: { 
  2.     nextQuestion() { 
  3.         this.currentIndex = (this.currentIndex + 1)  
  4.             % this.questions.length; 
  5.         // 把currentIndex插到隊首 
  6.         this.slideQueue.unshift(this.currentIndex); 
  7.         // 如果當前沒有滑動,則執(zhí)行滑動 
  8.         !this.sliding && this._slideToNext(); 
  9.     }, 

每次點擊按鈕都把待處理的currentIndex插到隊列里面,如果當前已經在滑動了,則不立刻執(zhí)行,否則執(zhí)行滑動_slideToNext函數:

  1. _slideToNext() { 
  2.     // 取出下一個要處理的元素 
  3.     let currentIndex = this.slideQueue.pop(); 
  4.     // 下一個slide的類型 
  5.     let nextType =  currentIndex % 2 ? "odd" : "even"
  6.     let $nextSlide = this.$refs[`${nextType}Task`].$el; 
  7.     $nextSlide.style.display = "block"
  8.     setTimeout(() => { 
  9.         $nextSlide.style.transform = "translateX(0)"
  10.         this.sliding = true
  11.         setTimeout(() => { 
  12.             this._updateData(currentIndex); 
  13.             // 如果當前還有未處理的元素, 
  14.             // 則繼續(xù)處理即繼續(xù)滑動 
  15.             if (this.slideQueue.length) { 
  16.                 // 要等到兩個component的DOM更新了 
  17.                 this.$nextTick(this._slideToNext); 
  18.             } else { 
  19.                 this.sliding = false
  20.             } 
  21.         }, 500); 
  22.     }, 0); 
  23. }, 

這個函數每次都先取出當前要處理的currentIndex,然后接下來的操作和第2點提到的一樣,只是在0.5s動畫結束后的異步回調里面需要判斷一下,當前隊列是否還有未處理的元素,如果有的話,需要繼續(xù)執(zhí)行_slideToNext,直到隊列空了。這個執(zhí)行需要掛在nextTick里面,因為需要等到兩個component的DOM更新了才能操作。

這樣理論上就沒問題了,但實際上還是有問題,感受如下:

我們發(fā)現有些slide沒有過渡效果,而且不是非必現的,沒有規(guī)律。經過一番排查,發(fā)現如果把上面的nextTick改成setTimeout情況就會好一些,并且setTimeout的時間越長,就越不會出現失去過渡效果的情況。但是這個不能從根本上解決問題,這里的原因應該是Vue的自動更新DOM和transition動畫不是很兼容,有可能是Vue的異步機制問題,也有可能是JS結合transition本身就有問題,但以前沒有遇到過,具體沒有深入排查。不管怎么樣,只能放棄使用CSS的transition做動畫。

如果有使用jQuery的話,可以使用jQuery的animation,如果沒有的話,那么可以使用原生dom的animate函數,如下代碼所示:

  1. _slideToNext(fast = false) { 
  2.     let currentIndex = this.slideQueue.pop(); 
  3.     // 下一個slide的類型 
  4.     let nextType =  currentIndex % 2 ? "odd" : "even"
  5.     // 獲取下一個slide的dom元素 
  6.     let $nextSlide = this.$refs[`${nextType}Task`].$el; 
  7.     $nextSlide.style.display = "block"
  8.     this.sliding = true
  9.     // 使用原生animate函數 
  10.     $nextSlide.animate([ 
  11.         // 關鍵幀 
  12.         {transform: "translateX(100%)"}, 
  13.         {transform: "translateX(0)"
  14.     ], { 
  15.         duration: fast ? 200 : 500, 
  16.         iteration: 1, 
  17.         easing: "ease" 
  18.     // 返回一個Animate對象,它有一個onfinish的回調 
  19.     }).onfinish = () => { 
  20.         // 等動畫結束后更新數據 
  21.         this._updateData(currentIndex); 
  22.         if (this.slideQueue.length) { 
  23.             this.$nextTick(() => { 
  24.                 this._slideToNext(true); 
  25.             }); 
  26.         } else { 
  27.             this.sliding = false
  28.         } 
  29.     }; 
  30. }, 

使用animate函數達到了和transition同樣的效果,并且還有一個onfinish的動畫結束回調函數。上面代碼還做了一個優(yōu)化,如果用戶點得很快的時候,縮短過渡動畫的時間,讓它切得更快一點,這樣看起來更自然一點。使用這樣的方式,就不會出現transition的問題了。***的效果如下:

這個體驗感覺已經比較流暢了。

原生animate不兼容IE/Edge/Safari,可以裝一個polyfill的庫,如這個web-animation,或者使用其它一些第三方的動畫庫,或自己用setInterval寫一個。

如果你要加上一題的按鈕,支持返回上一題,那么可能需要準備3個component,中間那個用于顯示,左右兩邊各跟著一個,準備隨時切過來。具體讀者可以自行嘗試。

這種模式除了答題的場景,還有多封郵件預覽、PPT展示等都可以用到,它除了有一個過渡的效果外,還能提前預加載下一個slide需要的圖片、音頻、視頻等資源,并且不會像傳統(tǒng)的輪播插件那樣一下子把所有的slide都渲染了。適用于slide比較多的情況,不需要太復雜的切換動畫。

【本文是51CTO專欄作者“人人網FED”的原創(chuàng)稿件,轉載請通過51CTO聯系原作者獲取授權】

戳這里,看該作者更多好文

責任編輯:武曉燕 來源: 51CTO專欄
相關推薦

2015-05-28 10:20:34

js相冊翻頁

2023-08-08 14:31:42

輪播圖鴻蒙

2024-03-20 09:40:27

動畫技巧CSS逐幀動畫

2021-12-06 15:10:01

鴻蒙HarmonyOS應用

2016-09-26 15:14:28

Javascript前端vue

2011-05-31 09:23:58

Android Activity

2022-06-16 09:55:58

css輪播圖

2016-12-01 09:24:56

Android

2022-06-01 22:30:15

滑動容器堆疊容器

2022-04-25 07:36:21

組件數據函數

2022-04-26 05:55:06

Vue.js異步組件

2024-03-15 09:27:35

函數數據懶加載FlowItem組件

2021-11-09 08:30:48

CSS 技巧巧用濾鏡

2021-03-10 15:03:40

鴻蒙HarmonyOS應用

2024-07-15 08:02:37

2024-08-19 09:55:06

2024-07-10 08:39:49

2022-09-19 19:16:42

輪播圖has

2020-12-09 07:54:17

Vue插件鼠標

2024-01-23 09:15:33

Vue3組件拖拽組件內容編輯
點贊
收藏

51CTO技術棧公眾號