滚动小球的实现,只需要考虑enter效果即可,用emit和on派发和监听事件,cartcontrol.vue派发事件{this.$emit('cart-add', event.target)};,goods.vue监听事件cart-add,并添加监听函数cartAdd<cartcontrol v-on:cart-add="cartAdd" :food="food"></cartcontrol>,在监听函数cartAdd中拿到shopcart的DOM对象 this.$refs.shopcart.drop(target);,返回shopcart.vue编写drop()获取下落小球的初始位置,并实现beforeEnter,enter和afterEnter方法,设置动画
1)在shopcart中定义小球,ball-container中存放小球,小球的状态存放在一个数组中,初始状态都是隐藏的
<div class="ball-container">
<div v-for="ball in balls" :key="ball.id">
<transition name="drop" @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter">
<div v-show="ball.show" class="ball">
<div class="inner inner-hook">
</div>
</div>
</transition>
</div>
</div>
2)为小球添加样式,ball位于购物车图片的上方.ball position:fix z-index:200并设置缓动函数: transition
设置inner,inner代表内层的一个小球,并设置缓动函数
3)此处没有设置enter和leave的动画,是因为要动态计算cartcontrol中加号的位置(即起始点宽高的位置,left或者是top的值),首先要拿到cartcontrol中加号这个元素。因为在cartcontrol点击加号会出发事件,我们可以在cartcontral中添加一个派发事件,将它的DOM对象传出来,即在点击事件中添加分发事件addCart(event) {this.$emit('cart-add', event.target)};
methods: {
addCart(event) {
//解决PC端双点击的问题
if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
return;
}
//console.log('click');//点击不生效,不要忘了在foodScroll中添加click: true
if (!this.food.count) {
//food.count是原json中不存在的属性,不能直接添加
//this.food.count = 1;
Vue.set(this.food, 'count', 1);//给this.food增加一个count属性,并初始化为1
} else {
this.food.count++;
}
//设置滚动对象时,点击加号,设置一个派发事件,将DOM对象传出去,将target(DOM)作为cart.add事件的对象传入
//$emit, $on, $off 分别来分发、监听、取消监听事件:
this.$emit('cart-add', event.target);
},
decreaseCart(event) {
//解决PC端双点击的问题
if (!event._constructed) { //浏览器直接return掉,去掉自带click事件的点击
return;
}
if (this.food.count) {
this.food.count--;
}
}
}
};
4)之后在父组件goods中对cart-add进行监听并添加处理事件cartAdd(传入target),<cartcontrol v-on:cart-add="cartAdd" :food="food"></cartcontrol>,cartAdd函数添加在methods中
cartAdd(target) {
//拿到traget(DOM对象)之后,将其传入shopcart组件中drop(target){}方法,
//此处用this.$refs调用子组件,访问DOM时用的是ref="menuWrapper"
this.$nextTick(() => { //回调函数异步执行,两个动画效果就不会卡顿了
this.$refs.shopcart.drop(target);
});
}
5)此处回到shopcart.vue编写drop(el){}方法,drop方法对应name="drop"的transition,并实现1)中的beforeEnter,enter和afterEnter方法,并在css中添加transition
methods: {
drop(el) {
//console.log(el); //验证是否能正确输出
//遍历balls,拿到第一个show为false的球,做一个动画
for (let i = 0; i < this.balls.length; i++) {
let ball = this.balls[i];
if (!ball.show) { //show为false的球
ball.show = true; //小球下落
ball.el = el;//保留当前的DOM对象,用来计算位置
this.dropBalls.push(ball); //dropBalls存的是已经下落的小球
return;
}
}
},
//定义三个钩子函数实现动画
beforeEnter(el) { //el为当前执行transition动画的DOM对象
//先找到所有为true的小球(连续点击的情况)
let count = this.balls.length;
while (count--) {
let ball = this.balls[count];
if (ball.show) { //这个是要运动的小球true
let rect = ball.el.getBoundingClientRect();//获得元素相当于视口的位置
let x = rect.left - 32;
let y = -(window.innerHeight - rect.top - 22);
el.style.display = ''; //v-show默认display:none,设置为空,让它显示
//外层元素是纵向的动画,内层元素是横向的动画
el.style.webkitTransform = `translate3d(0,${y}px,0)`;
el.style.transform = `translate3d(0,${y}px,0)`;
let inner = el.getElementsByClassName('inner-hook')[0];
inner.style.webkitTransform = `translate3d(${x}px, 0, 0)`;
inner.style.transform = `translate3d(${x}px, 0, 0)`;
}
}
},
enter(el) {
/* 触发浏览器重绘,重绘之后才可以设置transform*/
/* eslint-disable no-unused-vars */
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];
inner.style.webkitTransform = 'translate3d(0,0,0)';
inner.style.transform = 'translate3d(0,0,0)';
});
},
afterEnter(el) { //动画完成
let ball = this.dropBalls.shift();//删除并返回第一个ball
if (ball) {
ball.show = false; //重置ball.show的状态
el.style.display = 'none';
}
},
}
CSS:
.ball-container
.ball
position fixed //相对于视口做布局
left 32px
bottom 22px
z-index 200
transition: all 0.6s cubic-bezier(0.49, -0.29, 0.75, 0.41)
.inner
width 16px
height 16px
border-radius 50%
background rgb(0,160,220)
transition all 0.4s linear //x轴做一个线性的过渡即可