vue better-scroll
组件实现类似外卖点餐界面的左右侧菜单联动:即点击左侧使右侧滚动到对应位置,右侧滚动时选中左侧对应选项。
效果如下:
安装依赖:
npm install better-scroll npm install stylus stylus-loader@3.0.1 --save-dev
vue 文件代码:
<template> <div> <div > <div > <ul> <li v-for="(good, index) in goods" :key="index" : @click="clickMenuItem(index)" > <span > <img :src="good.icon" v-if="good.icon" /> {{ good.name }} </span> </li> </ul> </div> <div > <ul ref="foodsUl"> <li v-for="(good, index) in goods" :key="index" > <h1 >{{ good.name }}</h1> <ul> <li v-for="(food, index) in good.foods" :key="index" > <div > <img width="57" height="57" :src="food.icon" /> </div> <div > <h2 >{{ food.name }}</h2> <p >{{ food.description }}</p> <div > <span >月售{{ food.sellCount }}份</span> <span>好评率{{ food.rating }}%</span> </div> <div > <span >¥{{ food.price }}</span> <span v-if="food.oldPrice" >¥{{ food.oldPrice }}</span > </div> </div> </li> </ul> </li> </ul> </div> </div> </div> </template> <script> import BScroll from "better-scroll"; export default { data() { return { scrollY: 0, // 右侧滑动的 Y 轴坐标 (滑动过程时实时变化) tops: [], // 所有右侧分类 li 的 top 组成的数组 (列表第一次显示后就不再变化) goods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "优惠", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "瑶", description: "公主", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "安琪拉", description: "双马尾", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "折扣", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "小乔", description: "扇子", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "王昭君", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] }, { name: "法师", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "小乔", description: "扇子", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "王昭君", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "王昭君", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "王昭君", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] }, { name: "辅助", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "瑶", description: "公主", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "蔡文姬", description: "kkk", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "东皇", description: "hhh", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "孙膑", description: "666", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] }, { name: "射手", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "伽罗", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "后裔", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "狄仁杰", description: "hhh", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "公孙离", description: "666", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] }, { name: "打野", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "李白", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "百里玄策", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "韩信", description: "hhh", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "孙悟空", description: "666", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] }, { name: "坦克", foods: [ { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "亚瑟", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "项羽", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "凯", description: "hhh", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "夏侯惇", description: "666", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "亚瑟", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "项羽", description: "", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "凯", description: "hhh", sellCount: 1, rating: 100, price: 2, oldPrice: "6" }, { icon: "http://liangziqi.top/meme-img/126-430.jpg", name: "夏侯惇", description: "666", sellCount: 1, rating: 100, price: 2, oldPrice: "6" } ] } ] }; }, mounted() { this.$nextTick(() => { this._initScroll(); this._initTops(); }); }, computed: { // 计算得到当前分类的下标 currentIndex() { // 初始化和相关数据发生了变化时执行 // 得到条件数据 const { scrollY, tops } = this; // 根据条件计算产生一个结果 const index = tops.findIndex((top, index) => { // scrollY>=当前 top && scrollY<下一个 top return scrollY >= top && scrollY < tops[index + 1]; }); // 返回结果 return index; } }, methods: { // 初始化滚动 _initScroll() { // 列表显示之后创建 new BScroll(".menu-wrapper", { click: true }); this.foodsScroll = new BScroll(".foods-wrapper", { probeType: 2, // 因为惯性滑动不会触发 click: true }); // 给右侧列表绑定 scroll 监听 this.foodsScroll.on("scroll", ({ x, y }) => { console.log("scroll", x, y); this.scrollY = Math.abs(y); }); // 给右侧列表绑定 scroll 结束的监听 this.foodsScroll.on("scrollEnd", ({ x, y }) => { console.log("scrollEnd", x, y); this.scrollY = Math.abs(y); }); }, // 初始化 tops _initTops() { // 1. 初始化 tops const tops = []; let top = 0; tops.push(top); // 2. 收集 // 找到所有分类的 li const lis = this.$refs.foodsUl.getElementsByClassName("food-list-hook"); Array.prototype.slice.call(lis).forEach(li => { top += li.clientHeight; tops.push(top); }); // 3. 更新数据 this.tops = tops; console.log(tops); }, clickMenuItem(index) { console.log(index); // 使用右侧列表滑动到对应的位置 // 得到目标位置的 scrollY const scrollY = this.tops[index]; // 立即更新 scrollY(让点击的分类项成为当前分类) this.scrollY = scrollY; // 平滑滑动右侧列表 this.foodsScroll.scrollTo(0, -scrollY, 300); } } }; </script> <style lang="stylus" rel="stylesheet/stylus"> bottom-border-1px($color) { position: relative; border: none; &:after { content: ''; position: absolute; left: 0; bottom: 0; width: 100%; height: 1px; background-color: $color; transform: scaleY(0.5); } } .goods { display: flex; position: absolute; top: 15px; bottom: 46px; width: 100%; background: #fff; overflow: hidden; .menu-wrapper { flex: 0 0 80px; width: 80px; background: #f3f5f7; .menu-item { display: table; height: 54px; width: 56px; padding: 0 12px; line-height: 14px; &.current { position: relative; z-index: 10; margin-top: -1px; background: #fff; color: $green; font-weight: 700; } .icon { display: inline-block; vertical-align: top; width: 12px; height: 12px; margin-right: 2px; background-size: 12px 12px; background-repeat: no-repeat; } .text { display: table-cell; width: 56px; vertical-align: middle; bottom-border-1px(rgba(7, 17, 27, 0.1)); font-size: 12px; } } } .foods-wrapper { flex: 1; .title { padding-left: 14px; height: 26px; line-height: 26px; border-left: 2px solid #d9dde1; font-size: 12px; color: rgb(147, 153, 159); background: #f3f5f7; text-align: left; margin: 0; } .food-item { display: flex; margin: 18px; padding-bottom: 18px; bottom-border-1px(rgba(7, 17, 27, 0.1)); &:last-child { margin-bottom: 0; } .icon { flex: 0 0 57px; margin-right: 10px; } .content { flex: 1; text-align: left; .name { margin: 2px 0 8px 0; height: 14px; line-height: 14px; font-size: 14px; color: rgb(7, 17, 27); } .desc, .extra { line-height: 10px; font-size: 10px; color: rgb(147, 153, 159); } .desc { line-height: 12px; margin-bottom: 8px; } .extra { .count { margin-right: 12px; } } .price { font-weight: 700; line-height: 24px; .now { margin-right: 8px; font-size: 14px; color: rgb(240, 20, 20); } .old { text-decoration: line-through; font-size: 10px; color: rgb(147, 153, 159); } } } } } } li { list-style: none; } ul { padding: 0; margin: 0; } </style>
以上就是 vue better-scroll 组件实现类似外卖点餐界面的左右侧菜单联动效果,仅供参考。