1.加入购物车按钮组件
将加入购物车按钮部分抽离成一个组件,因为会多次复用到,即:
//carControl.vue
<template>
<div class="car-control">
<!-- 动画名称为move -->
<transition name="move">
<!-- 数量大于0时(即count属性存在时),减号出现,点击减号触发decreaseCar函数-->
<div class="car-decrease" v-show="food.count > 0" @click="decreaseCar">
<span class="icon inner"></span>
</div>
</transition>
<div class="car-count" v-show="food.count > 0">{{food.count}}</div>
<!-- 点击加号时触发addCar函数 -->
<div class="car-add" @click="addCar">
<span class="icon"></span>
</div>
</div>
</template>
<script>
import Vue from 'vue'
export default {
props: {
food: Object,
},
methods: {
addCar (event) {
if(!this.food.count) {//count不存在时将count设置为1
Vue.set(this.food, 'count', 1);
}else{
this.food.count++;
}
// 子组件向父组件传参,event.target是加号的dom
this.$emit('add', event.target);
},
decreaseCar () {
this.food.count--;
}
}
}
</script>
<style lang="less" scoped>
.car-control {
.car-decrease {
display: inline-block;
padding: 6px;
// 所有属性都将获得过渡效果,时间为0.4s,直线
transition: all 0.4s linear;
// 进入前和离开后的状态(平移)
transform: translate3d(0, 0, 0);
opacity: 1;
font-size: 0;
// 进入前和离开后的状态(旋转)
.inner {
text-align: center;
font-size: 24px;
line-height: 24px;
color: rgb(0, 160, 220);
transform: rotate(0deg);
}
// 在进入后和离开前的状态(平移)
&.move-enter, &.move-leave-active {
opacity: 0;
transform: translate3d(24px, 0, 0);
// 在进入后和离开前的状态(旋转)
.inner {
transform: rotate(180deg);
}
}
}
.car-count {
display: inline-block;
vertical-align: top;
width: 12px;
padding-top: 6px;
line-height: 24px;
text-align: center;
font-size: 10px;
color: rgb(147, 153, 159);
}
.car-add {
display: inline-block;
padding: 6px;
font-size: 24px;
line-height: 24px;
color: rgb(0, 160, 220);
}
}
</style>
2.购物车小球动画实现
点击加号时会在该处弹出一个小球,然后抛物线落入购物车中
原代码的思想是正序将小球的show依次变为true,然后倒序遍历,取到第一个为true的小球然后开始动画,这就存在一个bug,当动画的时间变长,五个小球全用完时,第一个小球落地变为false,再次点击时取到的是最后一个小球;现该bug已经改正
//shopCar.vue
<template>
<div>
<div class="shop-car">
<!-- 小球动画 -->
<div class="ball-container" v-for="(ball, index) in balls" :key="index">
<!-- 过度钩子函数 -->
<transition
@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>
</template>
<script>
import CarControl from '@/components/carControl/carControl'
export default {
props: {
deliveryPrice: {
type: Number,
default: 0,
},
minPrice: {
type: Number,
default: 0
},
selectFoods: {
type: Array,
default () {
return [];
}
}
},
data () {
return {
// 创建5个小球用于动画,即当动画未完成时调用其他小球完成动画
balls: [
{
show: false,
num: 1,
},
{
show: false,
num: 2,
},
{
show: false,
num: 3,
},
{
show: false,
num: 4,
},
{
show: false,
num: 5,
}
],
dropBalls: [],//存储下落的小球
count: '',//判定是第几个小球
}
},
methods: {
// 遍历小球,找到一个show属性为false的小球,将其的show设置成true并存入已下落得小球数组里
drop(el) {
// console.log(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);
this.count = i;
return;
}
}
},
// 动画开始时小球的状态
beforeDrop(el) {
let ball = this.balls[this.count];
// 元素相对于视口的距离
let rect = ball.el.getBoundingClientRect();
console.log(rect);
// x,y为初始点与目标点的差值
let x = rect.left - 32;
let y = -(window.innerHeight -rect.top -24);
// el.style.display = '';
// el (初始位置为 0,0,0)和购物车icon在一起,将小球(el) 放到加号位置去
//纵向动画
el.style.webkitTransform = `translate3d(0, ${y}px, 0)`;
el.style.transform = `translate3d(0, ${y}px, 0)`;
//横向动画 inner-hook, 仅仅定义类 dom选择器,不做样式
let inner = el.getElementsByClassName('inner-hook')[0];
inner.style.webkitTransform = `translate3d(${x}px, 0, 0)`;
inner.style.transform = `translate3d(${x}px, 0, 0)`;
},
// 动画完成时小球的状态
dropping (el, done) {
// 手动触发浏览器重绘,便于translate3d,--rf变量不会使用
let rf = el.offsetHeight;
this.$nextTick(() => {
//小球样式位置,置于购物车按钮位置处
el.style.webkitTransform = 'translate3d(0,0,0)';
el.style.transform = 'translate3d(0,0,0)';
let inner = el.getElementsByClassName('inner-hook')[0];
// 小球dom
inner.style.webkitTransform = 'translate3d(0,0,0)';
inner.style.transform = 'translate3d(0,0px,0)';
// done();//进入动画后立即触发
el.addEventListener("transitionend",done);//完成动画后触发
})
},
// 动画完成后的小球状态
afterDrop (el) {
// 此轮动画结束后,将此次的 ball 取出 ,ball状态重置,,el display:none
console.log(this.dropBalls);
let ball = this.dropBalls.shift();
if(ball) {
ball.show = false;
el.style.display = 'none';
}
},
}
}
</script>
<style lang="less" scoped>
//只显示相关的一部分
.ball-container {
.ball {
position: fixed;
left: 32px;
bottom: 24px;
z-index: 100;
//y 轴 贝塞尔曲线
transition: all 5s cubic-bezier(0.49, -0.29, 0.75, 0.41);
.inner {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: rgb(0, 160, 220);
transition: all 5s linear;
}
}
}
</style>
3.在父组件goods.vue中调用
//展示需要的一部分代码
<template>
<div class="goods">
<div class="menu-wrapper" ref="menuWrapper">
<ul>
<li class="menu-item vux-1px-b" v-for="(item, index) in goods" :class="{'current' : currentIndex == index}"
@click="selectGood(index, $event)">
<span class="text">
<span class="icon" :class="classMap[item.type]" v-show="item.type >= 0"></span>{{item.name}}
</span>
</li>
</ul>
</div>
<div class="foods-wrapper" ref="foodsWrapper">
<ul>
<li class="food-list" ref="foodList" v-for="(item, index) in goods">
<div class="title">{{item.name}}</div>
<ul>
<li class="food-item " :class='{"vux-1px-b":index!=item.foods.length-1}' v-for="(food,index) in item.foods" :key='index'>
<div class="icon" @click="seeFood(food, $event)">
<img width="57" height="57" :src="food.icon">
</div>
<div class="content">
<div class="name">{{food.name}}</div>
<div class="desc">{{food.description}}</div>
<div class="extra">
<span>月售{{food.sellCount}}份</span>
<span>好评率{{food.rating}}%</span>
</div>
<div class="price">
<span>¥{{food.price}}</span>
<span class="old-price" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
</div>
<div class="car-control-wrapper">
<CarControl :food="food" @add="addFood"></CarControl>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
<ShopCar ref="shopCar" :deliveryPrice="seller.deliveryPrice" :minPrice="seller.minPrice" :selectFoods="selectFoods"></ShopCar>
<Food @add="addFood" :food="seeFoodInfo" ref="food"></Food>
</div>
</template>
<script>
import BScroll from 'better-scroll'
import ShopCar from '@/components/goods/shopCar'
import CarControl from '@/components/carControl/carControl'
import Food from '@/components/food/food'
export default {
props: {
seller: Object,
},
components: {
ShopCar,
CarControl,
Food
},
data () {
return {
goods: [],
listHeight: [],
scrollY: 0,
seeFoodInfo: {},
}
},
created () {
//获取数据
this.$http.get('./api/goods').then((res) => {
this.goods = res.data.data;
this.$nextTick(() => {
this._initScroll();
this._calculateHeight();
})
});
this.classMap = ['decrease','discount','special','invoice','guarantee'];
},
methods: {
/*
CarControl子组件和Food子组件激发的事件
*/
addFood (target) {
this._drop(target);
},
/*
_drop方法,传入点击的dom对象
*/
_drop (target) {
//调用 shopcar 组件中的 drop 方法,向其传入当前点击的 dom 对象
// this.$refs.shopcart.drop(target);
// 体验优化,异步执行下落动画
this.$nextTick(() => {
this.$refs.shopCar.drop(target);
})
},
},
computed: {
//通过遍历,选出所有选中的商品
selectFoods () {
let foods = [];
this.goods.forEach((good) => {
good.foods.forEach((food) => {
if(food.count) {
foods.push(food)
}
})
})
return foods;
}
},
}
</script>