Vue之網易雲音樂橫向菜單的實現


title: Vue之網易雲音樂橫向菜單的實現 date: 2018-06-30 20:25:22 categories: Vue tags:前端

  • Vue
  • better-scroll
  • 橫向菜單 top: 100 copyright: true

以前在學習的時候有稍微搗鼓一下網易雲音樂,主要是爲了學習Vue,鞏固基礎知識,而後看到這個橫向菜單,固然我的也喜歡看球,因此每次看騰訊NBA的時候老是會想這個是這樣實現的,因而藉助以前還沒寫完的demo,完成這個橫向菜單的實現,廢話很少說,先上效果圖vue

網易雲音樂橫向菜單

從使用虎牙直播橫向菜單的體驗獲得,咱們的橫向菜單的業務邏輯以下:git

  1. 滑動菜單,並選擇菜單項;
  2. 選擇某個菜單項,該菜單項居中(去除邊界菜單項)

咱們的使用的better-scroll這個插件來實現,具體安裝請參考BetterScrollgithub

前端DOM結構api

<template>
  <div class="mv-tabs">
    <div class="tabs" ref="tabsWrapper">
      <ul ref="tab">
        <li v-for="(item, index) in tabs" :key="index" @click="selectItem(index)">
          <router-link tag="div" :to="item.to" class="tab-item">
            <span class="tab-link">{{item.title}}</span>
          </router-link>
        </li>
      </ul>
    </div>
  </div>
</template>
複製代碼

經過使用插件Vue來調試項目 bash

Vue DOM

其中tabs包括菜單項名和它的路由app

data () {
    return {
      tabs: [
        {
          to: '/mv/recommend-mv',
          title: '推薦'
        },
        {
          to: '/mv/music-mv',
          title: '音樂'
        },
        {
          to: 'show-mv',
          title: 'Show'
        },
        {
          to: '/mv/acg-mv',
          title: '二次元'
        },
        {
          to: '/mv/dance-mv',
          title: '舞蹈'
        },
        {
          to: '/mv/game-mv',
          title: '遊戲'
        },
        {
          to: '/mv/mvs',
          title: 'mv'
        }
      ],
      mX: 0, // tab移動的距離
      tabWidth: 80 // 每一個tab的寬度
    }
複製代碼

樣式異步

.mv-tabs
    position relative
    top -5.5rem
    bottom 0
    width 100%
    .tabs
      margin-top 3rem
      height 2.5rem
      width 100%
      line-height 2.5rem
      box-sizing border-box
      overflow hidden
      white-space nowrap
      .tab-item
        float left
        width 80px
        height 40px
        text-align center
        .tab-link
          padding-bottom 5px
          color #333333
        &.router-link-active
          color #d33a31
          border-bottom 2px solid #d33a31
          box-sizing border-box
複製代碼

樣式和DOM結構就不詳細講了,具體講實現吧 首先須要計算出這個菜單中全部內容的width,也就是包裹這個菜單的容器;接着初始化better-scroll,並忽略該實例對象的垂直方向的滑動.學習

methods: {
    _initMenu () {
      let tabsWidth = this.tabWidth
      let width = this.tabs.length * tabsWidth
      this.$refs.tab.style.width = `${width}px`
      this.$nextTick(() => {
        if (!this.scroll) {
          this.scroll = new BScroll(this.$refs.tabsWrapper, {
            scrollX: true,
            eventPassthrough: 'vertical' // 忽略這個實例對象的垂直滑動事件
          })
        } else {
          this.scroll.refresh()
        }
      })
    }
  }
複製代碼

這裏是第二個業務邏輯的思路(應該會有更好的思路,求大佬指點)ui

個人思路是這樣的:每個菜單項都會有各自的點擊移動操做,因此我是根據當前tabs的位置,經過點擊事件將tabs移動到它相應的位置,例如,中間菜單項在點擊時會移動到居中的位置。

methods: {
    selectItem (index) {
      let tabs = this.$refs.tab
      let moveX = +tabs.style.transform.replace(/[^0-9\-,]/g, '').split(',')[0]
      switch (index) {
        case 0:
          if (moveX <= 0 && moveX > -this.tabWidth) {
            this.mX = 0
          }
          break
        case 1:
          if (moveX <= 0 && moveX > -this.tabWidth * 2) {
            this.mX = 0
          }
          break
        case 2:
          if (moveX < 0 && moveX >= -this.tabWidth * 2) {
            this.mX = 0
          }
          break
        case 3:
          if (moveX <= 0 && moveX >= -this.tabWidth * 2) {
            this.mX = -this.tabWidth
          }
          break
        case 4:
          if (moveX <= 0 && moveX >= -this.tabWidth * 2) {
            this.mX = -this.tabWidth * 2
          } else if (moveX === 0) {
            this.mX = -this.tabWidth * 2
          }
          break
        case 5:
          if (moveX < 0 && moveX > -this.tabWidth * 2) {
            this.mX = -this.tabWidth * 2
          }
          break
        case 6:
          if (moveX > -this.tabWidth * 2 && moveX < -this.tabWidth * 3 / 2) {
            this.mX = -this.tabWidth * 2 + 10
          }
          break
        default:
          break
      }
      tabs.style.transform = `translate(${this.mX}px, 0)`
    }
  }
複製代碼

渲染的DOM結構

不少時候咱們在使用better-scroll的時候,發現這個實例對象已經初始化,可是不能滑動,是由於,Vue是異步更新數據的,因此咱們須要異步計算它實際內容的寬度或者高度,Vue提供一個了this.$nextTick()這個hock來實現,這個API是在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。

官方解釋:$nextTick

當生命鉤子mounted觸發時,初始化better-scroll

mounted () {
    this.$nextTick(() => {
      this._initMenu()
    })
}
複製代碼

所有代碼

<template>
  <div class="mv-tabs">
    <div class="tabs" ref="tabsWrapper">
      <ul ref="tab">
        <li v-for="(item, index) in tabs" :key="index" @click="selectItem(index)">
          <router-link tag="div" :to="item.to" class="tab-item">
            <span class="tab-link">{{item.title}}</span>
          </router-link>
        </li>
      </ul>
    </div>
  </div>
</template>

<script>
import BScroll from 'better-scroll'

export default {
  data () {
    return {
      tabs: [
        {
          to: '/mv/recommend-mv',
          title: '推薦'
        },
        {
          to: '/mv/music-mv',
          title: '音樂'
        },
        {
          to: 'show-mv',
          title: 'Show'
        },
        {
          to: '/mv/acg-mv',
          title: '二次元'
        },
        {
          to: '/mv/dance-mv',
          title: '舞蹈'
        },
        {
          to: '/mv/game-mv',
          title: '遊戲'
        },
        {
          to: '/mv/mvs',
          title: 'mv'
        }
      ],
      mX: 0,
      tabWidth: 80
    }
  },
  mounted () {
    this.$nextTick(() => {
      this._initMenu()
    })
  },
  methods: {
    selectItem (index) {
      let tabs = this.$refs.tab
      let moveX = +tabs.style.transform.replace(/[^0-9\-,]/g, '').split(',')[0]
      switch (index) {
        case 0:
          if (moveX <= 0 && moveX > -this.tabWidth) {
            this.mX = 0
          }
          break
        case 1:
          if (moveX <= 0 && moveX > -this.tabWidth * 2) {
            this.mX = 0
          }
          break
        case 2:
          if (moveX < 0 && moveX >= -this.tabWidth * 2) {
            this.mX = 0
          }
          break
        case 3:
          if (moveX <= 0 && moveX >= -this.tabWidth * 2) {
            this.mX = -this.tabWidth
          }
          break
        case 4:
          if (moveX <= 0 && moveX >= -this.tabWidth * 2) {
            this.mX = -this.tabWidth * 2
          } else if (moveX === 0) {
            this.mX = -this.tabWidth * 2
          }
          break
        case 5:
          if (moveX < 0 && moveX > -this.tabWidth * 2) {
            this.mX = -this.tabWidth * 2
          }
          break
        case 6:
          if (moveX > -this.tabWidth * 2 && moveX < -this.tabWidth * 3 / 2) {
            this.mX = -this.tabWidth * 2 + 10
          }
          break
        default:
          break
      }
      tabs.style.transform = `translate(${this.mX}px, 0)`
    },
    _initMenu () {
      let tabsWidth = this.tabWidth
      let width = this.tabs.length * tabsWidth
      this.$refs.tab.style.width = `${width}px`
      this.$nextTick(() => {
        if (!this.scroll) {
          this.scroll = new BScroll(this.$refs.tabsWrapper, {
            scrollX: true,
            eventPassthrough: 'vertical'
          })
        } else {
          this.scroll.refresh()
        }
      })
    }
  }
}
</script>

<style lang="stylus" scoped>
  .mv-tabs
    position relative
    top -5.5rem
    bottom 0
    width 100%
    .tabs
      margin-top 3rem
      height 2.5rem
      width 100%
      line-height 2.5rem
      box-sizing border-box
      overflow hidden
      white-space nowrap
      .tab-item
        float left
        width 80px
        height 40px
        text-align center
        .tab-link
          padding-bottom 5px
          color #333333
        &.router-link-active
          color #d33a31
          border-bottom 2px solid #d33a31
          box-sizing border-box
</style>
複製代碼

知乎

我的博客

Github

相關文章
相關標籤/搜索