以前在學習的時候有稍微搗鼓一下網易雲音樂,主要是爲了學習Vue,鞏固基礎知識,而後看到這個橫向菜單,固然我的也喜歡看球,因此每次看騰訊NBA的時候老是會想這個是這樣實現的,因而藉助以前還沒寫完的demo,完成這個橫向菜單的實現,廢話很少說,先上效果圖前端
從使用虎牙直播橫向菜單的體驗獲得,咱們的橫向菜單的業務邏輯以下:vue
咱們的使用的better-scroll這個插件來實現,具體安裝請參考BetterScrollgit
前端DOM結構github
<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來調試項目api
其中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() } }) } }
這裏是第二個業務邏輯的思路(應該會有更好的思路,求大佬指點)this
個人思路是這樣的:每個菜單項都會有各自的點擊移動操做,因此我是根據當前tabs的位置,經過點擊事件將tabs移動到它相應的位置,例如,中間菜單項在點擊時會移動到居中的位置。spa
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)` } }
不少時候咱們在使用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>