title: Vue之網易雲音樂橫向選單的實現 date: 2018-06-30 20:25:22 categories: Vue tags:
- Vue
- better-scroll
- 橫向選單 top: 100 copyright: true
之前在學習的時候有稍微搗鼓一下網易雲音樂,主要是為了學習Vue,鞏固基礎知識,然後看到這個橫向選單,當然個人也喜歡看球,所以每次看騰訊NBA的時候總是會想這個是這樣實現的,於是藉助之前還沒寫完的demo,完成這個橫向選單的實現,廢話不多說,先上效果圖
從使用虎牙直播橫向選單的體驗得到,我們的橫向選單的業務邏輯如下:
- 滑動選單,並選擇選單項;
- 選擇某個選單項,該選單項居中(去除邊界選單項)
我們的使用的better-scroll這個外掛來實現,具體安裝請參考BetterScroll
前端DOM結構
<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來除錯專案
其中tabs包括選單項名和它的路由
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()
}
})
}
}
複製程式碼
這裡是第二個業務邏輯的思路(應該會有更好的思路,求大佬指點)
我的思路是這樣的:每一個選單項都會有各自的點選移動操作,所以我是根據當前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)`
}
}
複製程式碼
很多時候我們在使用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>
複製程式碼