左右联动滚动
项目源码地址
https://github.com/zhou-X-boy/Vue_study/tree/master/VueProject/pdd
定义数据
在Shop.vue组件中定义数据
1:右侧列表滑动的Y轴坐标
2:右侧所有分类的头部位置
<template> //见源码 </template> <script> //1:引入vuex中从服务器端发送过来的数据,也就是state.js中的数据 import { mapState} from 'vuex'; //引入第三方插件库实现滚动效果 import BScroll from 'better-scroll' export default { name: 'Shop', data() { return { scrollY: 0, //右侧列表滑动的Y轴坐标 rightLiTops: [] //右侧所有分类的头部位置 } } } </script> <style scoped lang="stylus" rel="stylesheet/stylus"> //css样式见源码 </style>
求出右侧所有版块的头部位置
1:创建一个方法计算右侧所有版块的头部位置,
2:并在watch中进行监听这个方法,当数据发生变化时才开始执行这个方法,
<template> <div class="shop"> <!--左边--> <div class="menu-wrapper"> </div> <!--右边--> <div class="shop-wrapper"> <ul ref="shopsParent" > <li class="shops-li" v-for="(goods, index1) in searchgoods" :key="index1" > <div class="shops-title"> <h4>{{goods.name}}</h4> <a href="">查看更多 ></a> </div> <ul class="phone-type" v-if="goods.tag === 'phone'"> <li v-for="(phone,index3) in goods.category" :key="index3" > <img :src="phone.icon" alt=""> </li> </ul> <ul class="shops-items"> <li v-for="(item, index2) in goods.items" :key="index2"> <img :src="item.icon" alt=""> <span>{{item.title}}</span> </li> </ul> </li> </ul> </div> </div> </template> <script> //1:引入vuex中从服务器端发送过来的数据,也就是state.js中的数据 import { mapState} from 'vuex'; //引入第三方插件库实现滚动效果 import BScroll from 'better-scroll' export default { name: 'Shop', data() { return { scrollY: 0, //右侧列表滑动的Y轴坐标(实时更新) rightLiTops: [] //右侧所有分类的头部位置 } }, methods: { //1.2:求出右侧所有版块的头部位置 _initRightLiTops() { //1.2.1:创建临时数组 const tempArr = []; //1.2.2:定义一个新的变量记录高度 let top = 0; //1.2.3:将定义的变量加入到临时数组中 tempArr.push(top); //1.2.4:遍历右侧商品列表class为shops-li的li标签,成为一个类数组 let allLis = this.$refs.shopsParent.getElementsByClassName('shops-li'); /*1.2.5:转化为一个真数组,Array.prototype(数组原型) slice.call()方法将类数组转化为真正的数组 通过forEach()方法遍历这个数组 */ Array.prototype.slice.call(allLis).forEach(li => { //1.2.6:通过li.clientHeight()方法取得li的高度,并叠加计算每个li的高度 top += li.clientHeight; //1.2.7:将取得的每个li得高度加入到tempArr数组中 tempArr.push(top); }); //1.2.8:更新数据,将tempArr数组赋值给创建的空数组rightLiTops this.rightLiTops = tempArr; } }, //监听方法,当数据发生变化时,才被执行 watch: { //监听searchgoods数据变化 searchgoods() { //$nextTick()方法 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它 this.$nextTick(()=>{ //监听右侧所有版块的的头部位置 this._initRightLiTops(); }) } } } </script> <style scoped lang="stylus" rel="stylesheet/stylus"> @import "../../../common/stylus/mixins.styl" //css样式见源码 </style>
求出右侧列表滑动的Y轴坐标(实时更新)
利用better-scroll第三方库中带有的on方法监听滚动事件
<script> //1:引入vuex中从服务器端发送过来的数据,也就是state.js中的数据 import { mapState} from 'vuex'; //引入第三方插件库实现滚动效果 import BScroll from 'better-scroll' export default { name: 'Shop', data() { return { scrollY: 0, //右侧列表滑动的Y轴坐标(实时更新) rightLiTops: [] //右侧所有分类的头部位置 } }, methods: { //1.1:初始化方法 _initBScroll() { //1.1:左边 let leftScroll = new BScroll('.menu-wrapper', { scrollY: true, //当设置为 true 的时候,可以开启纵向滚动。 click: true }); //1.2:左边 //let rightScroll 改为 this.rightScroll 就讲局部的对象变为全局的对象 let rightScroll = new BScroll('.shop-wrapper', { scrollY: true, //当设置为 true 的时候,可以开启纵向滚动。 click: true }); //1.3:右侧滑动事件,通过better-scroll库中带有的on方法 this.rightScroll.on('scroll',()=>{ }) } }, width: { //监听searchgoods数据变化 searchgoods() { //$nextTick()方法 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它 this.$nextTick(()=>{ //1.1:监听初始化方法 this._initBScroll(); //1.2:监听右侧所有版块的的头部位置 this._initRightLiTops(); }) } } } </script>
显示右侧滚动CSS样式变化
current类:当滚动联动时,左侧被选中的选项的样式变化
<style scoped lang="stylus" rel="stylesheet/stylus"> @import "../../../common/stylus/mixins.styl" //其他css样式见源码 .current color #e02e24 .current::before //:before 在元素之前插入内容 content '' //插入的内容 background-color #e02e24 width 4px height 30px position absolute //绝对定位 left 0 </style>
动态绑定类
使class为shops-li的li标签动态绑定current类,当当前li标签的index===currentIndex计算属性获得的当前滚动到的index时,显示current类的样式
<template> <div class="shop"> <!--左边--> <div class="menu-wrapper"> <ul> <li class="menu-item" v-for="(goods, index) in searchgoods" :key="index" :class="{current: index === currentIndex}" > </li> </ul> </div> <!--右边--> <div class="shop-wrapper"> </div> </div> </template> <script> //1:引入vuex中从服务器端发送过来的数据,也就是state.js中的数据 import { mapState} from 'vuex'; //引入第三方插件库实现滚动效果 import BScroll from 'better-scroll' export default { name: 'Shop', data() { return { scrollY: 0, //右侧列表滑动的Y轴坐标(实时更新) rightLiTops: [] //右侧所有分类的头部位置 } }, methods: { //1.1:初始化方法 _initBScroll() { //1.2:左边 //let rightScroll 改为 this.rightScroll 就讲局部的对象变为全局的对象 this.rightScroll = new BScroll('.shop-wrapper', { scrollY: true, //当设置为 true 的时候,可以开启纵向滚动。 click: true, /* * 有时候我们需要知道滚动的位置。 * 当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件; * 当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件; * 当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。 * 如果没有设置该值,其默认值为 0,即不派发 scroll 事件。 */ probeType: 3 }); }, //监听方法,当数据发生变化时,才被执行 watch: { }, //计算属性 computed: { //用于动态决定左侧哪个选项被选中 currentIndex() { //1:获取数据 const {scrollY,rightLiTops} = this; //2:遍历rightLiTops数组,取出索引 /* *findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。 *当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置, * 如果没有符合条件的元素返回 -1 */ return rightLiTops.findIndex((LiTop,index)=> { return scrollY >= LiTop && scrollY <= rightLiTops[index + 1] }) } } } </script> <style scoped lang="stylus" rel="stylesheet/stylus"> @import "../../../common/stylus/mixins.styl" //css样式见源码 .current color #e02e24 .current::before //:before 在元素之前插入内容 content '' //插入的内容 background-color #e02e24 width 4px height 30px position absolute //绝对定位 left 0 </style>
实现点击左侧商品列表,右侧商品列表滚动
使用第三方库better-scroll带有的**scrollTo(x, y, time, easing, extraTransform)**方法来实现效果
- {number} x 横轴坐标(单位 px)
- {number} y 纵轴坐标(单位 px)
- {number} time 滚动动画执行的时长(单位 ms)
- {Object} easing 缓动函数,一般不建议修改,如果想修改,参考源码中的
packages/shared-utils/src/ease.ts
里的写法
监听左侧商品列表的点击事件
<template> <div class="shop"> <!--左边--> <div class="menu-wrapper"> <ul> <li class="menu-item" v-for="(goods, index) in searchgoods" :key="index" :class="{current: index === currentIndex}" @click = "clickLeftItem(index)" > </li> </ul> </div> <!--右边--> <div class="shop-wrapper"> </div> </div> </template> <script> //1:引入vuex中从服务器端发送过来的数据,也就是state.js中的数据 import { mapState} from 'vuex'; //引入第三方插件库实现滚动效果 import BScroll from 'better-scroll' export default { name: 'Shop', data() { return { scrollY: 0, //右侧列表滑动的Y轴坐标(实时更新) rightLiTops: [] //右侧所有分类的头部位置 } }, methods: { //1.1:初始化方法 _initBScroll() { //1.1:左边 let leftScroll = new BScroll('.menu-wrapper', { scrollY: true, //当设置为 true 的时候,可以开启纵向滚动。 click: true }); //1.2:右边 //let rightScroll 改为 this.rightScroll 就讲局部的对象变为全局的对象 this.rightScroll = new BScroll('.shop-wrapper', { scrollY: true, //当设置为 true 的时候,可以开启纵向滚动。 click: true, /* * 有时候我们需要知道滚动的位置。 * 当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件; * 当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件; * 当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。 * 如果没有设置该值,其默认值为 0,即不派发 scroll 事件。 */ probeType: 3 }); //1.3:右侧滑动事件,通过better-scroll库中带有的on(type, fn, context)方法 this.rightScroll.on('scroll', (pos)=>{ //通过Math.abs(int a)方法取得Y轴 ,返回int 值的绝对值。如果参数为非负数,则返回该参数。如果参数为负数,则返回该参数的相反数。 this.scrollY = Math.abs(pos.y); // console.log(this.scrollY) }); this.rightScroll.on('scrollEnd', (pos)=>{ this.scrollY = Math.abs(pos.y); }) }, }, //监听方法,当数据发生变化时,才被执行 watch: { }, //计算属性 computed: { //用于动态决定左侧哪个选项被选中 currentIndex() { //1:获取数据 const {scrollY,rightLiTops} = this; //2:遍历rightLiTops数组,取出索引 /* *findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。 *当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置, * 如果没有符合条件的元素返回 -1 */ return rightLiTops.findIndex((liTop,index)=> { return scrollY >= liTop && scrollY < rightLiTops[index + 1] }) } } } </script> <style scoped lang="stylus" rel="stylesheet/stylus"> @import "../../../common/stylus/mixins.styl" //css样式见源码 </style>
实现滚动右侧商品列表,左侧商品列表滚动
使用第三方库better-scroll带有的**scrollToElement(el, time, offsetX, offsetY, easing)**方法来实现效果
{DOM | string} el 滚动到的目标元素, 如果是字符串,则内部会尝试调用 querySelector 转换成 DOM 对象。
{number} time 滚动动画执行的时长(单位 ms)
{number | boolean} offsetX 相对于目标元素的横轴偏移量,如果设置为 true,则滚到目标元素的中心位置
{number | boolean} offsetY 相对于目标元素的纵轴偏移量,如果设置为 true,则滚到目标元素的中心位置
{Object} easing 缓动函数,一般不建议修改,如果想修改,参考源码中的
packages/shared-utils/src/ease.ts
里的写法<template> <div class="shop"> <!--左边--> <div class="menu-wrapper"> <ul> <li class = "menu-item" v-for = "(goods, index) in searchgoods" :key = "index" :class = "{current: index === currentIndex}" @click = "clickLeftItem(index)" ref = "menulist" > <span>{{goods.name}}</span> </li> </ul> </div> <!--右边--> <div class="shop-wrapper"> <ul ref="shopsParent" > </ul> </div> </div> </template> <script> export default { name: "Shop", data() { return { scrollY: 0, //右侧列表滑动的Y轴坐标(实时更新) rightLiTops: [] //所有分类的头部位置 } }, //生命周期函数,组件创建后调用 mounted() {}, methods: { //1.4:滚动右侧商品列表切换左侧商品列表 _leftScroll(index){ //取得左侧li标签 let menuLists = this.$refs.menulist; //取得滚动右侧联动左侧当前索引所在的li标签 let el = menuLists[index]; //console.log(el); this.leftScroll.scrollToElement(el,300,0,-100); } }, //计算属性 computed: { //3:获取搜索列表数据 ...mapState(['searchgoods']), //用于动态决定左侧哪个选项被选中 currentIndex() { //1:获取数据 const {scrollY,rightLiTops} = this; //2:遍历rightLiTops数组,取出索引 /* *findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。 *当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置, * 如果没有符合条件的元素返回 -1 */ return rightLiTops.findIndex((liTop,index)=> { //绑定左侧索引位置滚动 this._leftScroll(index); return scrollY >= liTop && scrollY < rightLiTops[index + 1] }) } }, //监听方法,数据发生变化,才开始执行 watch: {}, } </script>