项目中经常遇到区域超出部分会出现滚动条,滚动条在pc端可以通过鼠标滚轮控制上下,在移动端可以通过鼠标拖动页面进行滚动,这两种场景都是符合用户习惯,然而这种滚动条一般都是竖【vertical】项滚动条,如果pc端出现横向滚动条【horizontal】,在不做处理的情况下,你只能用鼠标拖动横向滚动条按钮【scrollerbar】展示滚动区域,而且为了美观,一般滚动条会进行样式编写或者隐藏,那么横向区域默认情况下就没法滚动。
二、描述
现为了解决pc端滚动区域能像移动端一样,能够通过鼠标拖动滚动区域直接进行滚动,如图所示
pc端滚动示例图
滚动实例用到知识点如下:
采用vue-cli3+iscroll.js组合的方式;
使用 vue 自定义指令实现 iscroll 实例化和参数配置;
实现横向滚动区域和竖向滚动区域之间的联动;
实现横向滚动条居中显示和使用scrollIntoView()方法的差别
三、自定义指令 v-iscroll
1、新建指令文件
这里使用 vue 自定义指令初始化 iscroll 实例,在 vue-cli3 项目目录下新建vIscroll.js,文件代码如下:
const IScroll = require('iscroll') const VIScroll = { install: function (Vue, options) { Vue.directive('iscroll', { inserted: function (el, binding, vnode) { let callBack let iscrollOptions = options <!--vue组件中绑定的两个参数 option、instance--> const option = binding.value && binding.value.option const func = binding.value && binding.value.instance // 判断输入参数 const optionType = option ? [].toString.call(option) : undefined const funcType = func ? [].toString.call(func) : undefined // 兼容 google 浏览器拖动 el.addEventListener('touchmove', function (e) { e.preventDefault() }) // 将参数配置到new IScroll(el, iscrollOptions)中 if (optionType === '[object Object]') { iscrollOptions = option } if (funcType === '[object Function]') { callBack = func } // 使用vnode绑定iscroll是为了让iscroll对象能够夸状态传递,避免iscroll重复建立 // 这里面跟官方网站 const myScroll = new IScroll('#wrapper',option) 初始化一样 vnode.scroll = new IScroll(el, iscrollOptions) // 如果指令传递函数进来,把iscroll实例传递出去 if (callBack) callBack(vnode.scroll) }, componentUpdated: function (el, binding, vnode, oldVnode) { // 将scroll绑定到新的vnode上,避免多次绑定 vnode.scroll = oldVnode.scroll // 使用 settimeout 让refresh跳到事件流结尾,保证refresh时数据已经更新完毕 setTimeout(() => { vnode.scroll.refresh() }, 0) }, unbind: function (el, binding, vnode, oldVnode) { // 解除绑定时要把iscroll销毁 vnode.scroll = oldVnode.scroll vnode.scroll.destroy() vnode.scroll = null } }) } } module.exports = VIScroll
这里附上 iscroll.js 5 官方文档地址, iscroll npm 包地址,相关属性和方法自行查看。
2、加载引用指令
首先在 main.js 中加载指令:
import Vue from 'vue' import App from './App.vue' import "./assets/reset.css" // 加载scroll指令 import VIscroll from './directive/vIscroll' Vue.use(VIscroll) Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')
使用指令,摘自 tabList.vue 组件部分代码如下:
<template> <div> <div v-iscroll="{ option: iscrollConf, instance: getIscroll }" ref="scrollContainer" > <ul ref="tabLiContainer" > <li v-for="(item, index) in list" :key="item.id" :id="item.id" ref="tabItem" @click="tabEvent(item, index)" > <div :class="{ 'item-active': currentId == item.id }" >{{item.num}}</div> </li> </ul> </div> <div @click="tabBtnEvent('left')" ><</div> <div @click="tabBtnEvent('right')" >></div> </div> </template> <script> export default { props: ['list'], data () { return { iscrollConf: { bounce: true, mouseWheel: true, click: true, scrollX: true, scrollY: false }, currentId: null, currentIndex: 0, myScroll: null } }, mounted () { this.$refs.tabLiContainer.style.width = this.$refs.tabItem[0].offsetWidth * this.list.length + 'px' this.$nextTick(() => { this.myScroll.refresh() }) }, methods: { tabEvent (item, currentIndex) { <!--点击某个li 按钮事件处理逻辑--> }, tabBtnEvent (direction) { <!--左右切换逻辑事件--> }, getIscroll (iscroll) { this.myScroll = iscroll } }, watch: { list: { handler (l) { this.currentId = l[0].id }, immediate: true, deep: true } } } </script> <style scoped> // 样式 </style>
上述代码中 v-iscroll 指令传入两个字段参数: