面向Vue新人:使用Vue寫一個圖片輪播元件

limingru發表於2018-03-19

之前一直都沒有認真的寫過一個元件。以前在寫業務程式碼的過程中,都是用的別人封裝好的元件,這次嘗試著寫了一個圖片輪播元件,雖然比不上知名的輪播元件,但它的功能基本完整,而且在寫這個元件的過程中,學的東西也很多,在這裡也給大家分享出來,如有疏漏,歡迎指正!

在製作這個元件之前,筆者google了不少關於輪播的文章,發現實現一個輪播的思路雖然各有不同,但是大的邏輯其實差不多,本文主要依據慕課網上焦點輪播圖特效這節課,不過慕課網主要用原生JS寫,而筆者則用Vue進行了重構,並且進行了一點修改。完成後的元件效果圖如下:

面向Vue新人:使用Vue寫一個圖片輪播元件

一、理清思路,理解需求和原理

1. 要寫一個什麼樣的輪播?

  • 在點選右側箭頭時,圖片向左滑動到下一張;點選左側箭頭時,圖片向右滑到下一張
  • 點選下面的小圓點,滑到對應的圖片,相應小圓點的樣式也發生改變
  • 要有過渡效果,要緩緩滑動過去
  • 當滑鼠hover到圖片上時,輪播暫停,當滑鼠leave時,輪播繼續
  • 自動播放功能
  • 無限滾動,即在滾動到最後一張時,再點選下一張時會繼續向左滑動到第一張,而不是整個拉到第一張,這裡有點難

2. 理解無限輪播的原理

我們先看下原理圖:

面向Vue新人:使用Vue寫一個圖片輪播元件

圖中紅線區域即是我們看到的圖片,這個輪播只展示5張圖片,但是在它的首尾各還有兩張圖片,在圖1前面放置了圖5,在圖5後面放置了圖1,之所以這麼做,是為了做無限滾動。無限滾動的原理在於:當整個圖向左側滾動到右邊的圖5時,會繼續向前走到圖1,在完全顯示出圖1後,會以肉眼看不到的速度向右側拉回到最左邊的圖1。 這樣,即使再向左側滑動看到的就是圖2了。

如下圖:在最後的圖1完成過渡完全顯示出來後,再將整個列表瞬間向右拉到左側的圖1。另一張邊界圖圖5的滾動也是,不過方向相反。

面向Vue新人:使用Vue寫一個圖片輪播元件

面向Vue新人:使用Vue寫一個圖片輪播元件

二、先讓圖片切換起來

1. 佈局和準備

<template>
  <div id="slider">
    <div class="window">   // window上圖中紅線框
      <ul class="container" :style="containerStyle">  //注意這裡的:style //這是圖片列表,排成一排
        <li>  //列表最前面的輔助圖,它和圖5一樣,用於無限滾動
          <img :src="sliders[sliders.length - 1].img" alt="">
        </li>
        <li v-for="(item, index) in sliders" :key="index">  //通過v-for渲染的需要展示的5張圖
          <img :src="item.img" alt="">
        </li>
        <li>  //列表最後面的輔助圖,它和圖1一樣,用於無限滾動
          <img :src="sliders[0].img" alt="">
        </li>
      </ul>
      <ul class="direction">  //兩側的箭頭
        <li class="left">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M481.233 904c8.189 0 16.379-3.124 22.628-9.372 12.496-12.497 12.496-32.759 0-45.256L166.488 512l337.373-337.373c12.496-12.497 12.496-32.758 0-45.255-12.498-12.497-32.758-12.497-45.256 0l-360 360c-12.496 12.497-12.496 32.758 0 45.255l360 360c6.249 6.249 14.439 9.373 22.628 9.373z"  /></svg>          
        </li>
        <li class="right">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M557.179 904c-8.189 0-16.379-3.124-22.628-9.372-12.496-12.497-12.496-32.759 0-45.256L871.924 512 534.551 174.627c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0l360 360c12.496 12.497 12.496 32.758 0 45.255l-360 360c-6.249 6.249-14.439 9.373-22.628 9.373z"  /></svg>          
        </li>
      </ul>
      <ul class="dots">  //下面的小圓點
        <li v-for="(dot, i) in sliders" :key="i" 
        :class="{dotted: i === (currentIndex-1)}"
        >
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: 'slider',
  data () {
    return {
      sliders:[
        {
          img:'../../static/images/1.jpg'
        },
        {
          img:'../../static/images/2.jpg'
        },
        {
          img:'../../static/images/3.jpg'
        },
        {
          img:'../../static/images/4.jpg'
        },
        {
          img:'../../static/images/5.jpg'
        }
      ],
      currentIndex:1,
      distance:-600
    }
  },
  computed:{
    containerStyle() {  //這裡用了計算屬性,用transform來移動整個圖片列表
      return {
        transform:`translate3d(${this.distance}px, 0, 0)`
      }
    }
  }
}
</script>

複製程式碼

好了,佈局大概就是這樣,效果圖如下:

面向Vue新人:使用Vue寫一個圖片輪播元件

上面的程式碼已經做了註釋,有幾個點在這裡再提一下:

  • window是紅線框,寬度為600px,它不會動,移動的是包裹著圖片的container,它的移動方式用:style="containerStyle",這是一個計算屬性,用transform:translate3d(${this.distance, 0, 0})來控制左右移動
  • data裡的distancecurrentIndex是關鍵,distance控制著移動的距離,預設是-600,顯示7張圖片中的第二張,也就是圖1。currentIndex是window顯示的圖片的索引,這裡預設是1,也是7張圖片中第2張。
  • 需要展示的只有5張圖片,但是在圖1前了一張圖5、在圖5後面放了一張圖1來做無限滾動,原理前面說過了
  • 當點選右側的箭頭,container向左移動,distance會越來越小;當點選左側的箭頭,container向右移動,distance會越來越大,方向不要弄錯

2. 圖片切換

我們在左側和右側的箭頭上新增點選事件:

      <ul class="direction">
        <li class="left" @click="move(600, 1)">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M481.233 904c8.189 0 16.379-3.124 22.628-9.372 12.496-12.497 12.496-32.759 0-45.256L166.488 512l337.373-337.373c12.496-12.497 12.496-32.758 0-45.255-12.498-12.497-32.758-12.497-45.256 0l-360 360c-12.496 12.497-12.496 32.758 0 45.255l360 360c6.249 6.249 14.439 9.373 22.628 9.373z"  /></svg>          
        </li>
        <li class="right" @click="move(600, -1)">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M557.179 904c-8.189 0-16.379-3.124-22.628-9.372-12.496-12.497-12.496-32.759 0-45.256L871.924 512 534.551 174.627c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0l360 360c12.496 12.497 12.496 32.758 0 45.255l-360 360c-6.249 6.249-14.439 9.373-22.628 9.373z"  /></svg>          
        </li>
      </ul>
      
      ......
      
      methods:{
            move(offset, direction) {
                this.distance += this.distance * direction
                if (this.distance < -3000) this.distance = -600
                if (this.distance > -600) this.distance = -3000
            }
      }
      
複製程式碼

解釋下上面的程式碼:點選左側或者右側的箭頭,呼叫move函式,move接收偏移量offset和方向direction兩個引數。direction只傳兩個值,1表示container向右移動,-1表示container向左移動;偏移量是600,也就是一張圖片的寬度。如果移動到7張圖片的最後一張,就把container拉到7張圖片裡的第二張;如果移動到7張圖片裡第一張,就把container拉到7張圖片裡的第5張。

效果:

面向Vue新人:使用Vue寫一個圖片輪播元件

可以看到,圖片切換效果已經出來了,但是下面的小圓點沒有跟著變換。接下來我們把這個效果加上。從上面的html程式碼可以看到,:class="{dotted: i === (currentIndex - 1)}",小圓點的切換效果和data裡的currentIndex值相關,我們只要隨著圖片切換變動currentIndex值就可以了。

修改move方法裡的程式碼:

......

move(offset, direction) {
    direction === -1 ? this.currentIndex++ : this.currentIndex--
    if (this.currentIndex > 5) this.currentIndex = 1
    if (this.currentIndex < 1) this.currentIndex = 5
    this.distance = this.distance + offset * direction
    if (this.distance < -3000) this.distance = -600
    if (this.distance > -600) this.distance = -3000
    }

複製程式碼

上面的新增的三行程式碼很好理解,如果是點選右側箭頭,container就是向左移動,this.currentIndex就是減1,反之就是加1。

效果:

面向Vue新人:使用Vue寫一個圖片輪播元件

可以看到,小圓點的切換效果已經出來了。

三、過渡動畫

上面的程式碼已經實現了切換,但是沒有動畫效果,顯的非常生硬,接下來就是給每個圖片的切換過程新增過渡效果。

這個輪播元件筆者並沒有使用Vue自帶的class鉤子,也沒有直接使用css的transition屬性,而是用慕課網原作者講的setTimeout方法加遞迴來實現。

其實我也試過使用Vue的鉤子,但是總有一些小問題解決不掉;比如下面找到的這個例子:例子

這個例子在過渡的邊界上有一些問題,我也遇到了,而且還是時有時無。而如果使用css的transition過渡方法,在處理邊界的無限滾動上總會在chrome瀏覽器上有一下閃動,即使新增了-webkit-transform-style:preserve-3d;-webkit-backface-visibility:hidden也還是沒用,而且要配合transition的transitionend事件對於IE瀏覽器的支援也不怎麼好。

如果大家有看到更好的辦法,請在評論中留言哦~

下面我們來寫這個過渡效果,主要是改寫:

methods:{
    move(offset, direction) {
        direction === -1 ? this.currentIndex++ : this.currentIndex--
        if (this.currentIndex > 5) this.currentIndex = 1
        if (this.currentIndex < 1) this.currentIndex = 5

        const destination = this.distance + offset * direction
        this.animate(destination, direction)
    },
    animate(des, direc) {
        if ((direc === -1 && des < this.distance) || (direc === 1 && des > this.distance)) {
            this.distance += 30 * direc        
            window.setTimeout(() => {
                this.animate(des, direc)
            }, 20)
        } else {
            this.distance = des
            if (des < -3000) this.distance = -600
            if (des > -600) this.distance = -3000
      }
    }
}
複製程式碼

上面的程式碼是這個輪播我覺得最麻煩、也是最難理解的地方。

來理解一下:首先,我們對於move方法進行了改寫,因為要一點點的移動,所以要先算出要移動到的目標距離。然後,我們寫一個animate函式來實現這個過渡。這個animate函式接收兩個引數,一個是要移動到的距離,另一個是方向。如果我們點選了右側的箭頭,container要向左側移動,要是沒有移動到目標距離,就在this.distance減去一定的距離,如果減去後還是沒有到達,在20毫米以後再呼叫這個this.animate,如此不斷移動,就形成了過渡效果。而如果移動到了目標距離,那就將目標距離賦值給this.distance,然後再進行邊界和無限滾動的判斷。

當然,使用window.setInterval()也可以實現這個效果,而且會稍微好理解一點,因為沒有用到遞迴:

methods:{
    move(offset, direction) {
        direction === -1 ? this.currentIndex++ : this.currentIndex--
        if (this.currentIndex > 5) this.currentIndex = 1
        if (this.currentIndex < 1) this.currentIndex = 5

        const destination = this.distance + offset * direction
        this.animate(destination, direction)
    },
    animate(des, direc) {
        const temp = window.setInterval(() => {
            if ((direc === -1 && des < this.distance) || (direc === 1 && des > this.distance)) {
                this.distance += 30 * direc
            } else {
                window.clearInterval(temp)
                this.distance = des
                if (des < -3000) this.distance = -600
                if (des > -600) this.distance = -3000
            }
        }, 20)
    }  
}
複製程式碼

實現出來的效果如下:

面向Vue新人:使用Vue寫一個圖片輪播元件

四、簡單節流一下

寫到這裡,效果是出來了,但是會有一點問題,如果多次快速點選,就會有可能出現下面這種情況:

面向Vue新人:使用Vue寫一個圖片輪播元件

出現這種情況的原因很簡單,因為是使用定時器過渡,所以連續快速點選就會出現錯亂,簡單節流一下就好了:在過渡完成之前點選箭頭無效,其實就是設了一個閘,第一次點選把閘開啟,在閘再次開啟之前,讓一部分程式碼無法執行,然後再在恰當的時機把閘開啟。

我們把這個閘設在move函式裡:

move(offset, direction) {
    if (!this.transitionEnd) return  //這裡是閘
    this.transitionEnd = false       //開閘以後再把閘關上
    direction === -1 ? this.currentIndex++ : this.currentIndex--
    if (this.currentIndex > 5) this.currentIndex = 1
    if (this.currentIndex < 1) this.currentIndex = 5

    const destination = this.distance + offset * direction
    this.animate(destination, direction)
}
複製程式碼

this.transitionEnd是這個閘的鑰匙,我們把它放到data裡:

this.transitionEnd: true
複製程式碼

這個閘一開始預設的狀態是開著的,第一次點選以後,這個閘就關上了,this.tranisitonEnd = false,在再次開啟之前,後面的程式碼都執行不了。接下來就是在恰當的時機把這個閘開啟,而這個恰當的時機就是過渡完成時,也就是在animate函式裡:

animate(des, direc) {
    if (this.temp) { 
        window.clearInterval(this.temp)
        this.temp = null 
    }
    this.temp = window.setInterval(() => {
        if ((direc === -1 && des < this.distance) || (direc === 1 && des > this.distance)) {
          this.distance += 30 * direc  
        } else {
          this.transitionEnd = true      //閘再次開啟
          window.clearInterval(this.temp)
          this.distance = des
          if (des < -3000) this.distance = -600
          if (des > -600) this.distance = -3000
        }
    }, 20)
}      
複製程式碼

這下快速點選就沒有之前的那個問題了:

面向Vue新人:使用Vue寫一個圖片輪播元件

五、點選小圓點實現圖片過渡切換

到目前為止的程式碼:

<template>
  <div id="slider">
    <div class="window">
      <ul class="container" :style="containerStyle">
        <li>
          <img :src="sliders[sliders.length - 1].img" alt="">
        </li>
        <li v-for="(item, index) in sliders" :key="index">
          <img :src="item.img" alt="">
        </li>
        <li>
          <img :src="sliders[0].img" alt="">
        </li>
      </ul>
      <ul class="direction">
        <li class="left" @click="move(600, 1)">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M481.233 904c8.189 0 16.379-3.124 22.628-9.372 12.496-12.497 12.496-32.759 0-45.256L166.488 512l337.373-337.373c12.496-12.497 12.496-32.758 0-45.255-12.498-12.497-32.758-12.497-45.256 0l-360 360c-12.496 12.497-12.496 32.758 0 45.255l360 360c6.249 6.249 14.439 9.373 22.628 9.373z"  /></svg>          
        </li>
        <li class="right" @click="move(600, -1)">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M557.179 904c-8.189 0-16.379-3.124-22.628-9.372-12.496-12.497-12.496-32.759 0-45.256L871.924 512 534.551 174.627c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0l360 360c12.496 12.497 12.496 32.758 0 45.255l-360 360c-6.249 6.249-14.439 9.373-22.628 9.373z"  /></svg>          
        </li>
      </ul>
      <ul class="dots">
        <li v-for="(dot, i) in sliders" :key="i" 
        :class="{dotted: i === (currentIndex-1)}"
        >
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: 'slider',
  data () {
    return {
      sliders:[
        {
          img:'../../static/images/1.jpg'
        },
        {
          img:'../../static/images/2.jpg'
        },
        {
          img:'../../static/images/3.jpg'
        },
        {
          img:'../../static/images/4.jpg'
        },
        {
          img:'../../static/images/5.jpg'
        }
      ],
      currentIndex:1,
      distance:-600,
      transitionEnd: true
    }
  },
  computed:{
    containerStyle() {
      return {
        transform:`translate3d(${this.distance}px, 0, 0)`
      }
    }
  },
  methods:{
    move(offset, direction) {
      if (!this.transitionEnd) return
      this.transitionEnd = false
      direction === -1 ? this.currentIndex++ : this.currentIndex--
      if (this.currentIndex > 5) this.currentIndex = 1
      if (this.currentIndex < 1) this.currentIndex = 5

      const destination = this.distance + offset * direction
      this.animate(destination, direction)
    },
    animate(des, direc) {
      if (this.temp) { 
        window.clearInterval(this.temp)
        this.temp = null 
      }
      this.temp = window.setInterval(() => {
        if ((direc === -1 && des < this.distance) || (direc === 1 && des > this.distance)) {
          this.distance += 30 * direc
        } else {
          this.transitionEnd = true
          window.clearInterval(this.temp)
          this.distance = des
          if (des < -3000) this.distance = -600
          if (des > -600) this.distance = -3000
        }
      }, 20)
    }
  }
}
</script>
複製程式碼

接下來我們要實現點選下面的小圓點來實現過渡和圖片切換。

<ul class="dots">
    <li v-for="(dot, i) in sliders" :key="i" 
    :class="{dotted: i === (currentIndex-1)}"
    @click = jump(i+1)>
    </li>
</ul>
複製程式碼

在點選小圓點的時候我們呼叫jump函式,並將索引i+1傳給它。這裡需要特別注意,小圓點的索引和圖片對應的索引不一致,圖片共7張,而5個小圓點對應的是圖片中中間的5張,所以我們才傳i+1

jump(index) {
    const direction = index - this.currentIndex >= 0 ? -1 : 1  //獲取滑動方向 
    const offset = Math.abs(index - this.currentIndex) * 600   //獲取滑動距離
    this.move(offset, direction)
}
複製程式碼

上面的程式碼有一個問題,在jump函式裡呼叫move方法,move裡對於currentIndex的都是+1,而點選小圓點可能是將currentIndex加或者減好多個,所以要對move裡的程式碼修改下:

direction === -1 ? this.currentIndex += offset/600 : this.currentIndex -= offset/600
複製程式碼

改一行,根據offset算出currentIndex就行了。

但是又有一個問題,長距離切換速度太慢,如下:

面向Vue新人:使用Vue寫一個圖片輪播元件

所以我們需要控制一下速度,讓滑動一張圖片耗費的時間和滑動多張圖片耗費的時間一樣,給move和animate函式新增一個speed引數,還要再算一下:

jump(index) {
    const direction = index - this.currentIndex >= 0 ? -1 : 1
    const offset = Math.abs(index - this.currentIndex) * 600
    const jumpSpeed = Math.abs(index - this.currentIndex) === 0 ? this.speed : Math.abs(index - this.currentIndex) * this.speed 
    this.move(offset, direction, jumpSpeed)
}
複製程式碼

六、自動播放與暫停

前面的寫的差不多了,到這裡就非常簡單了,寫一個函式play:

play() {
    if (this.timer) {
        window.clearInterval(this.timer)
        this.timer = null
    }
    this.timer = window.setInterval(() => {
        this.move(600, -1, this.speed)
    }, 4000)
}
複製程式碼

除了初始化以後自動播放,還要通過mouseover和mouseleave來控制暫停與播放:

stop() {
    window.clearInterval(this.timer)
    this.timer = null
}
複製程式碼

七、 兩處小坑

1. window.onblurwindow.onfocus

寫到這裡,基本功能都差不多了。但是如果把頁面切換到別的頁面,導致輪播圖所在頁面失焦,過一段時間再切回來會發現輪播狂轉。原因是頁面失焦以後,setInterval停止執行,但是如果切回來就會一次性把該走的一次性走完。解決的方法也很簡單,當頁面失焦時停止輪播,頁面聚焦時開始輪播。

window.onblur = function() { this.stop() }.bind(this)
window.onfocus = function() { this.play() }.bind(this)
複製程式碼

2. window.setInterval()小坑

當定時器window.setInterval()在多個非同步回撥中使用時,就有可能在某種機率下開啟多個執行佇列,所以為了保險起見,不僅應該在該清除時清除定時器,還要在每次使用之前也清除一遍

八、用props簡單寫兩個對外介面

props: {
    initialSpeed: {
      type: Number,
      default: 30
    },
    initialInterval: {
      type: Number,
      default: 4
    }
},
data() {
    ......
    speed: this.initialSpeed    
},
computed:{
    interval() {
        return this.initialInterval * 1000
    }
}

複製程式碼

然後再在相應的地方修改下就可以了。

完整的程式碼如下:

<template>
  <div id="slider">
    <div class="window" @mouseover="stop" @mouseleave="play">
      <ul class="container" :style="containerStyle">
        <li>
          <img :src="sliders[sliders.length - 1].img" alt="">
        </li>
        <li v-for="(item, index) in sliders" :key="index">
          <img :src="item.img" alt="">
        </li>
        <li>
          <img :src="sliders[0].img" alt="">
        </li>
      </ul>
      <ul class="direction">
        <li class="left" @click="move(600, 1, speed)">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M481.233 904c8.189 0 16.379-3.124 22.628-9.372 12.496-12.497 12.496-32.759 0-45.256L166.488 512l337.373-337.373c12.496-12.497 12.496-32.758 0-45.255-12.498-12.497-32.758-12.497-45.256 0l-360 360c-12.496 12.497-12.496 32.758 0 45.255l360 360c6.249 6.249 14.439 9.373 22.628 9.373z"  /></svg>          
        </li>
        <li class="right" @click="move(600, -1, speed)">
          <svg class="icon" width="30px" height="30.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M557.179 904c-8.189 0-16.379-3.124-22.628-9.372-12.496-12.497-12.496-32.759 0-45.256L871.924 512 534.551 174.627c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0l360 360c12.496 12.497 12.496 32.758 0 45.255l-360 360c-6.249 6.249-14.439 9.373-22.628 9.373z"  /></svg>          
        </li>
      </ul>
      <ul class="dots">
        <li v-for="(dot, i) in sliders" :key="i" 
        :class="{dotted: i === (currentIndex-1)}"
        @click = jump(i+1)
        >
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  name: 'slider',
  props: {
    initialSpeed: {
      type: Number,
      default: 30
    },
    initialInterval: {
      type: Number,
      default: 4
    }
  },
  data () {
    return {
      sliders:[
        {
          img:'../../static/images/1.jpg'
        },
        {
          img:'../../static/images/2.jpg'
        },
        {
          img:'../../static/images/3.jpg'
        },
        {
          img:'../../static/images/4.jpg'
        },
        {
          img:'../../static/images/5.jpg'
        }
      ],
      currentIndex:1,
      distance:-600,
      transitionEnd: true,
      speed: this.initialSpeed
    }
  },
  computed:{
    containerStyle() {
      return {
        transform:`translate3d(${this.distance}px, 0, 0)`
      }
    },
    interval() {
      return this.initialInterval * 1000
    }
  },
  mounted() {
    this.init()
  },
  methods:{
    init() {
      this.play()
      window.onblur = function() { this.stop() }.bind(this)
      window.onfocus = function() { this.play() }.bind(this)
    },
    move(offset, direction, speed) {
      if (!this.transitionEnd) return
      this.transitionEnd = false
      direction === -1 ? this.currentIndex += offset/600 : this.currentIndex -= offset/600
      if (this.currentIndex > 5) this.currentIndex = 1
      if (this.currentIndex < 1) this.currentIndex = 5

      const destination = this.distance + offset * direction
      this.animate(destination, direction, speed)
    },
    animate(des, direc, speed) {
      if (this.temp) { 
        window.clearInterval(this.temp)
        this.temp = null 
      }
      this.temp = window.setInterval(() => {
        if ((direc === -1 && des < this.distance) || (direc === 1 && des > this.distance)) {
          this.distance += speed * direc
        } else {
          this.transitionEnd = true
          window.clearInterval(this.temp)
          this.distance = des
          if (des < -3000) this.distance = -600
          if (des > -600) this.distance = -3000
        }
      }, 20)
    },
    jump(index) {
      const direction = index - this.currentIndex >= 0 ? -1 : 1
      const offset = Math.abs(index - this.currentIndex) * 600
      const jumpSpeed = Math.abs(index - this.currentIndex) === 0 ? this.speed : Math.abs(index - this.currentIndex) * this.speed 
      this.move(offset, direction, jumpSpeed)
    },
    play() {
      if (this.timer) {
        window.clearInterval(this.timer)
        this.timer = null
      }
      this.timer = window.setInterval(() => {
        this.move(600, -1, this.speed)
      }, this.interval)
    },
    stop() {
      window.clearInterval(this.timer)
      this.timer = null
    }
  }
}
</script>
複製程式碼

github地址

九、結語

大概寫完了這個元件,發現其實還有許多地方可以優化,this.distancethis.currentIndex耦合性很高,完全可以通過計算屬性連到一起。還有過渡方式,用定時器的方法還是有些生硬,沒有發揮出Vue的優勢來。不過,第一個元件算是寫完了,也費了一番力氣。

這是我在掘金上的第四篇文章,感謝閱讀!

相關文章