Vue之网易云音乐横向菜单的实现

简介: 之前在学习的时候有稍微捣鼓一下网易云音乐,主要是为了学习Vue,巩固基础知识,然后看到这个横向菜单,当然个人也喜欢看球,所以每次看腾讯NBA的时候总是会想这个是这样实现的,于是借助之前还没写完的demo,完成这个横向菜单的实现,废话不多说,先上效果图

title: Vue之网易云音乐横向菜单的实现 date: 2018-06-30 20:25:22 categories: Vue tags:

  • Vue
  • better-scroll
  • 横向菜单 top: 100 copyright: true

之前在学习的时候有稍微捣鼓一下网易云音乐,主要是为了学习Vue,巩固基础知识,然后看到这个横向菜单,当然个人也喜欢看球,所以每次看腾讯NBA的时候总是会想这个是这样实现的,于是借助之前还没写完的demo,完成这个横向菜单的实现,废话不多说,先上效果图

944dd776d714059ccf07d0caaeee4420c55e2bef

从使用虎牙直播横向菜单的体验得到,我们的横向菜单的业务逻辑如下:

  1. 滑动菜单,并选择菜单项;
  2. 选择某个菜单项,该菜单项居中(去除边界菜单项)

我们的使用的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来调试项目


c823ca03baab48fd98909079b762ce01979c7214

其中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)`
    }
  }

4f462fe52dd685546911573a92b560c630ed2a66

很多时候我们在使用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>


原文发布时间为:2018年06月30日
原文作者:Rain120
本文来源: 掘金  如需转载请联系原作者
相关文章
|
19天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
34 1
vue学习第四章
|
19天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
28 1
vue学习第九章(v-model)
|
19天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
33 1
vue学习第十章(组件开发)
|
25天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
25天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
25天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
25天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
24天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
38 3
|
26天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
24天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
50 2