公司提出需求要做一个点餐系统
需求:
点击左侧菜单 右侧滑动到相应菜品分类位置, 右侧滑动时滑动到哪个分类左侧相应分类高亮并且向中部滚动
使用 vue + better-scroll
1、项目下载 better-scroll
npm install better-scroll --save
2、在需要页面或组件引入
import BScroll from 'better-scroll'
因嘴笨直接贴代码了... DOM 结构
<template>
<div class="Dishes" id="Dishes">
<div class="header">
<div class="search">
<div class="left">
<img class="picSea" src="../assets/search.png" alt="">
<van-cell-group>
<van-field v-model="value" type="search" placeholder="搜索菜品名" class="searchFood"/>
</van-cell-group>
</div>
</div>
</div>
<div :class="[{afterScroll: afterScroll}, 'bb']">
<div class="set">
<div class="place">
<h3>苏宁广场店</h3>
<p>距离您约556.8km</p>
</div>
<div class="way" @click="changeWay">
<p :class="way ? 'active' : ''">堂食</p>
<p :class="!way ? 'active' : ''">外卖</p>
<div class="blackBoll" :style="{left: blackBoll}"></div>
</div>
</div>
<div class="advertising">
<img src="../assets/aaa.png" alt="">
</div>
</div>
<div class="shop" id="shop">
<!-- 左边 -->
<div class="menu-wrapper" :style="{height: clientHeightY + 'px'}">
<ul>
<!-- current -->
<li
class="menu-item"
v-for="(goods,index) in searchgoods"
:key="index"
:class="{active: index === currentIndex}"
@click="clickList(index)"
ref="menuList"
>
<span>{{goods}}</span>
</li>
</ul>
</div>
<!-- 右边 -->
<div class="shop-wrapper" :style="{height: clientHeightY + 'px'}">
<ul ref="itemList" class="food-item">
<li class="shops-li food-list" v-for="(goods, index1) in searchgoods" :key="index1">
<div class="shops-title">
<h4>{{goods}}</h4>
</div>
<ul>
<li v-for="(it, ind) in 8" :key="ind">
<div class="foodImg">
<img src="../assets/shopImg.png" alt="">
</div>
<div class="foodPrices">
<div class="title">店铺招牌百威啤酒</div>
<div class="good">好评 99+</div>
<div class="info">
<div>
<p>¥</p>
<p>10</p>
<p>/份</p>
<p>¥15</p>
</div>
<div>
<i @click="additem" class="iconfont icontianjia"></i>
</div>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
<!-- 购物车 -->
<div class="footer">
<div class="ball-container">
<!--小球-->
<div v-for="(ball, index) in balls" :key="index">
<transition
name="drop"
@before-enter="beforeDrop"
@enter="dropping"
@after-enter="afterDrop"
>
<div class="ball" v-show="ball.show">
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
</div>
<div class="shoppingCar" @click="showShopCar">
<i :class="{addBig: addBig}" class="iconfont icongouwuche"></i>
<div class="total">
<div>总计</div>
<span>¥</span>
<div class="money">200</div>
</div>
</div>
<div class="submit" @click="submit">
确定下单
</div>
</div>
<!-- 购物车弹框 -->
<van-action-sheet v-model="show" style="z-inde: 5!important;" @close="onClose">
<div class="buyCar">
<div class="title">
清空购物车
</div>
<ul>
<li v-for="(item, index) in 3" :key="index">
<div class="shopImg">
<img src="../assets/shopImg.png" alt="">
</div>
<div class="shopInfo">
<h3>店铺招牌百威啤酒</h3>
<p>(约500毫升)+冰块</p>
</div>
<div class="shopNum">
10
</div>
</li>
</ul>
</div>
</van-action-sheet>
</div>
</template>
js 代码
<script>
import BScroll from 'better-scroll'
export default {
data() {
return {
addBig: false,
afterScroll: false,
oldHeight: 0,
clientHeightY: 400,
ortherHeight: 0,
value: '',
isScroll: false,
way: true,
clientWidth: 0,
blackBoll: '0px',
show: false,
searchgoods: [],
scrollY: 0, //右侧列表滑动的y轴坐标
rightLiTops:[], //所有分类头部位置
balls: [
//小球 多设置几个 因为可能多次点击
{
show: false
},
{
show: false
},
{
show: false
},
{
show: false
},
{
show: false
},
{
show: false
},
],
dropBalls: []
}
},
computed: {
//动态绑定class类名
currentIndex(index) {
const {scrollY,rightLiTops} = this;
return rightLiTops.findIndex((tops,index )=>{
this._initLeftScroll(index);
return scrollY >= tops && scrollY < rightLiTops[index + 1]
})
}
},
created () {
this.searchgoods = ['aa', 'bb', 'cc', 'dd', 'ff', 'ee', 'ss', 'vggv', 'asd', 'hgt', 'sad', 'asda', 'iojo', 'asd']
},
mounted () {
// 为了滑动区域高度适应所有机型
let height = document.body.clientHeight
let width = document.body.clientWidth
this.clientWidth = width
this.clientHeightY = height - width / 750 * 450
this.oldHeight = this.clientHeightY
this.ortherHeight = this.clientHeightY + this.clientWidth / 750 * 220
console.log(this.clientHeightY)
//监听数据
this.$nextTick(() =>{
//左右两边滚动
this. _initBScroll();
//右边列表高度
this._initRightHeight()
})
},
methods: {
additem(event) {
this.drop(event.target);
// this.count++;
setTimeout(() => {
this.addBig = true
setTimeout(() => {
this.addBig = false
}, 100)
}, 400)
},
drop(el) {
//抛物
for (let i = 0; i < this.balls.length; i++) {
let ball = this.balls[i];
if (!ball.show) {
ball.show = true;
ball.el = el;
this.dropBalls.push(ball);
return;
}
}
},
beforeDrop(el) {
/* 购物车小球动画实现 */
let count = this.balls.length;
while (count--) {
let ball = this.balls[count];
if (ball.show) {
let rect = ball.el.getBoundingClientRect(); //元素相对于视口的位置
let x = rect.left - 60;
let y = -(window.innerHeight - rect.top - 44); //获取y
el.style.display = "";
el.style.webkitTransform = "translateY(" + y - 4 + "px)"; //translateY
el.style.transform = "translateY(" + y + "px)";
let inner = el.getElementsByClassName("inner-hook")[0];
inner.style.webkitTransform = "translateX(" + x + "px)";
inner.style.transform = "translateX(" + x + "px)";
}
}
},
dropping(el, done) {
/*重置小球数量 样式重置*/
let rf = el.offsetHeight;
el.style.webkitTransform = "translate3d(0,0,0)";
el.style.transform = "translate3d(0,0,0)";
let inner = el.getElementsByClassName("inner-hook")[0];
inner.style.webkitTransform = "translate3d(0,0,0)";
inner.style.transform = "translate3d(0,0,0)";
el.addEventListener("transitionend", done);
},
afterDrop(el) {
/*初始化小球*/
let ball = this.dropBalls.shift();
if (ball) {
ball.show = false;
el.style.display = "none";
}
},
_initBScroll() {
//左边滚动
this.leftBscroll = new BScroll('.menu-wrapper',{click: true});
//右边滚动
this.rightBscroll = new BScroll('.shop-wrapper',{
probeType:3,
click: true,
bounce: false
});
//监听右边滚动事件
this.rightBscroll.on('scroll',(pos) => {
this.scrollY = Math.abs(pos.y);
if (this.scrollY >= 30) {
this.afterScroll = true
this.clientHeightY = this.ortherHeight
} else if (this.scrollY >= 3 || this.scrollY === 0) {
this.afterScroll = false
this.clientHeightY = this.oldHeight
}
})
},
//求出右边列表的高度
_initRightHeight () {
let itemArray = []; //定义一个伪数组
let top = 0;
itemArray.push(top)
//获取右边所有li的礼
let allList = this.$refs.itemList.getElementsByClassName('shops-li');
//allList伪数组转化成真数组
Array.prototype.slice.call(allList).forEach(li => {
top += li.clientHeight; //获取所有li的每一个高度
itemArray.push(top)
});
this.rightLiTops = itemArray;
// console.log(this.rightLiTops)
},
//点击左边实现滚动
clickList(index){
console.log('111')
this.scrollY = this.rightLiTops[index];
console.log(this.scrollY)
this.rightBscroll.scrollTo(0, -this.scrollY, 600,)
},
//左右联调
_initLeftScroll(index){
let menu = this.$refs.menuList;
let el = menu[index];
this.leftBscroll.scrollToElement(el, 200 , 0, true)
},
submit() {
this.$router.push('/order')
},
onClose() {
this.show = false
},
showShopCar() {
this.show = true
},
changeWay() {
this.way = !this.way
this.way ? this.blackBoll = '0px' : this.blackBoll = '38px'
},
}
}
</script>
css 样式 预编译语言用的less 适配过rem 基准宽度750
使用的适配插件 postcss-pxtorem
<style scoped lang="less">
@keyframes changeBig {
from {
transform: scale(1.2);
}
to {
transform: scale(1);
}
}
.addBig {
animation: changeBig 0.2s;
// transform: scale(1.2);
// transition: all 0.1s;
}
.ball{
position: fixed;
left: 120px;
bottom: 60px;
z-index: 200;
transition: all 0.4s cubic-bezier(0.49, -0.29, 0.75, 0.41); /*贝塞尔曲线*/
}
.inner{
width: 30px;
height: 30px;
border-radius: 50%;
background-color: red;
transition: all 0.4s linear;
}
.bb {
height: 220px;
transition: all 0.5s;
}
.afterScroll {
opacity: 0;
transition: all 0.5s;
height: 0;
}
.Dishes {
height: 100vh;
box-sizing: border-box;
padding-top: 102px;
}
.shop {
position: relative;
top: 0;
padding: 0;
background: #fff;
z-index: 10;
}
.menu-wrapper {
position: absolute;
left: 0;
top: 0;
overflow: hidden;
height: 800px;
transition: all 0.5s;
background: #FFFFFF;
box-shadow:10px 0 15px -15px rgba(0, 0, 0, 0.5);
ul {
padding-bottom: 40px;
li {
width: 140px;
text-align: center;
line-height: 80px;
}
.active {
background: #FFE103;
}
}
}
.shop-wrapper {
position: absolute;
left: 140px;
top: 0;
overflow: hidden;
background: #FFFFFF;
transition: all 0.5s;
height: 800px;
.food-item {
padding-bottom: 80px;
.shops-li {
width: 610px;
// text-align: center;
// line-height: 800px;
// height: 800px;
.shops-title {
padding: 40px 0 0 40px;
}
}
}
}
.header {
width: 100%;
background: #FECF02;
position: fixed;
z-index: 5;
top: 0;
padding: 0 0 0 60px;
box-sizing: border-box;
box-shadow:inset 0px 15px 10px -15px rgba(0, 0, 0, 0.3);
}
.header .search {
width: 100%;
height: 102px;
padding: 16px 0 0 20px;
margin: 0 auto;
box-sizing: border-box;
}
.header .search .left {
width: 580px;
height: 66px;
background: #ffffff;
display: flex;
justify-content: left;
border-radius: 33px;
align-items: center;
}
.header .search .left .searchFood {
width: 400px;
line-height: 44px;
}
.header .search .right {
display: flex;
width: 180px;
font-size: 28px;
justify-content: left;
align-items: center;
}
.header .search .right i {
font-size: 10px;
margin: 8px 0 0 8px;
}
.header .search .picSea {
width: 40px;
height: 40px;
margin-left: 50px;
/* position: absolute;
top: 34px;
left: 130px; */
}
.header .selector ul{
width: 100%;
height: 62px;
display: flex;
justify-content: space-around;
font-size: 24px;
}
.header .selector ul li {
display: flex;
justify-content: left;
align-items: center;
}
.header .selector ul li i {
margin: 6px 0 0 6px;
font-size: 24px;
}
.set {
width: 100%;
height: 110px;
padding: 0px 84px;
// padding-top: 110px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
font-size: 22px;
align-items: center;
}
.set .place h3 {
font-size: 26px;
font-weight: 600;
margin-bottom: 10px;
}
.set .place p {
color: #9FA0A0;
}
.advertising img {
width: 100%;
height: 110px;
}
.set .way {
width: 150px;
height: 50px;
display: flex;
line-height: 50px;
box-sizing: border-box;
justify-content: space-between;
border: 1px solid #000000;
border-radius: 25px;
font-weight: 600;
position: relative;
z-index: 2;
}
.set .way p {
width: 50%;
text-align: center;
position: relative;
z-index: 3;
}
.blackBoll {
position: absolute;
background: #000;
border-radius: 25px;
width: 80px;
height: 50px;
z-index: 0;
top: 0;
transition: all 0.3s;
}
.set .way .active {
/* width: 60%; */
border-radius: 25px;
color: #ffffff;
font-weight: 600;
}
/* .left_menu {
} */
// .menu-wrapper {
// width: 142px;
// position: absolute;
// top: 330px;
// /* background: #FECF02; */
// box-shadow:10px 0 15px -15px rgba(0, 0, 0, 0.3);
// }
// .menu-item {
// height: 100px;
// line-height: 100px;
// text-align: center;
// }
.foods-wrapper {
width: 608px;
height: 860px;
position: absolute;
right: 0px;
top: 330px;
/* background: hotpink; */
}
.food-list h3{
// line-height: 30px;
padding-left: 80px;
color: #666;
font-size: 24px;
/* background: #FECF02; */
}
.food-list h3:nth-child(1) {
margin-top: 20px;
}
.food-list ul:nth-last-child(1) {
// padding-bottom: 80x;
}
.food-list ul li {
width: 540px;
height: 214px;
margin: 0 auto;
margin-top: 30px;
border-radius: 20px;
box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.1);
padding: 24px 40px 22px 28px;
box-sizing: border-box;
display: flex;
justify-content: left;
}
.food-list ul li:nth-last-child(1) {
// margin-bottom: 40px;
}
.food-list ul li .foodImg img {
width: 168px;
height: 168px;
margin-right: 10px;
}
.food-list ul li .foodPrices {
flex: 1;
i {
font-size: 40px;
}
}
.food-list ul li .foodPrices .title{
font-size: 24px;
font-weight: 600;
line-height: 60px;
}
.food-list ul li .foodPrices .good {
font-size: 18px;
color: #9FA0A0;
margin-bottom: 25px;
}
.food-list ul li .foodPrices .info {
display: flex;
justify-content: space-between;
}
.food-list ul li .foodPrices .info div:nth-child(1) {
height: 30px;
width: 140px;
position: relative;
}
.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(1) {
font-size: 18px;
font-weight: 600;
position: absolute;
bottom: 0px;
}
.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(2) {
font-size: 36px;
position: absolute;
bottom: -5px;
font-weight: 600;
left: 20px;
}
.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(3) {
font-size: 18px;
position: absolute;
bottom: 0;
font-weight: 600;
left: 68px;
}
.food-list ul li .foodPrices .info div:nth-child(1) p:nth-child(4) {
font-size: 18px;
color: #9FA0A0;
text-decoration:line-through;
position: absolute;
bottom: -2px;
left: 120px;
}
.current {
background-color: #FECF02;
}
.footer {
width: 100%;
height: 133px;
position: fixed;
bottom: 0;
background: #000000;
display: flex;
justify-content: space-between;
z-index: 201500;
}
.footer .shoppingCar {
display: flex;
justify-content: center;
align-items: center;
flex: 1;
}
.footer .shoppingCar .total {
color: #ffffff;
font-size: 24px;
width: 150px;
height: 40px;
display: flex;
justify-content: left;
position: relative;
}
.footer .shoppingCar .total div:nth-child(1) {
margin-right: 10px;
line-height: 44px;
}
.footer .shoppingCar .total span {
font-size: 20px;
position: absolute;
bottom: 4px;
left: 60px;
}
.footer .shoppingCar .total .money {
font-size: 36px;
position: absolute;
bottom: 0;
left: 88px;
}
.footer .shoppingCar i {
color: #FECF02;
font-size: 66px;
margin-right: 28px;
}
.footer .submit{
width: 290px;
line-height: 133px;
background: #FFE103;
font-size: 36px;
font-weight: 600;
text-align: center;
}
.buyCar {
padding-bottom: 132px;
box-sizing: border-box;
}
.buyCar .title {
height: 80px;
background: #FECF02;
text-align: right;
line-height: 80px;
padding-right: 42px;
color: #ffffff;
font-size: 24px;
font-weight: 600;
}
.buyCar ul {
padding: 0 42px;
box-sizing: border-box;
}
.buyCar ul li {
padding: 20px 0;
display: flex;
box-sizing: border-box;
justify-content: space-between;
border-bottom: 1px solid #FBF8FB;
}
.buyCar ul li .shopImg img{
width: 90px;
height: 90px;
margin-right: 24px;
}
.buyCar ul li .shopInfo {
flex: 1;
}
.buyCar ul li .shopInfo h3 {
font-size: 24px;
font-weight: 600;
color: #000000;
margin-bottom: 16px;
margin-top: 6px;
}
.buyCar ul li .shopInfo p {
font-size: 18px;
color: #9FA0A0;
}
::-webkit-scrollbar {
width:0;
height:0;
color:transparent;
}
</style>