在商品页面,当我们添加商品的时候,即点击“+”按钮,会有一个小球做抛物线的动作降到左下角的购物车里面的动作,现在就讲讲如何实现小球做抛物线的动作。
小球的动作解析:外层做纵轴方向的抛物线动作;内层做水平方向的线性的动作
难点:是在底部的shopCart.vue中获取到小球下落的位置,也就是对应的点击的"+"的位置,然后再shopCart.vue中描写小球下落的动作。
大概思路是:有3个.vue文件,父组件goods.vue,子组件cartControl.vue和子组件shopCart.vue。首先小球的下降的时机(也就是小球下落的位置)是在点击cartControl.vue中的“+”按钮触发的,那么需要将这个触发的对象从cartControl中传到父组件goods.vue中,那么需要在cartControl.vue中的触发“+”的函数中,书写this.$emit(add,event.target),然后在goods.vue中调用cartControl组件的地方绑定该函数,从而让父组件拿到子组件中被点击的对象,如
在cartControl.vue中的methods属性中定义的函数addCart(event)添加this.$emit('add',event.target)语句,其中add是在父组件goods.vue中的调用子组件cartControl.vue组件地方,通过@add绑定的监听事件属性,event.target是被点击的对象
//点击food右边的“+”
addCart(event){
if( !event._constructed ){
//不是自己开发的函数,则返回,避免在pc端触发两次
return
}
if( !this.food.count ){
// 如果this.foo.count的值为undefined,表示food对象中还没有count这个属性
// this.food.count = 1;
// set(对象,属性,属性值),让手动添加的属性,被浏览器识别监听
Vue.set(this.food,'count',1);
}else{
this.food.count++;
}
//添加一个商品时,通过$emit(event,[...args]),将DOM对象传给父组件
this.$emit('add', event.target);
}
在goods.vue中调用子组件cart-control的地方绑定监听事件add,其中addFood(target)是在父组件goods.vue中的methods定义的方法
<cart-control @add="addFood" :food="food"></cart-control>
拿到的点击的对象,就是小球要下落的位置,然后再父组件goods.vue中的methods属性中定义函数addFood(),这个函数主要负责把子组件cartControl.vue中传过来的点击的对象传给父组件,然后父组件在将其点击的对象传给子组件shopCart.vue中定义的函数,这样在shopCart.vue中就可以描写小球下落的动作了
//监听右侧添加食品的动作事件,把cartControl.vue中点击的对象通过target
//传到父组件goods.vue文件里面
addFood(target){
//然后在goods.vue中拿到子对象,从而可以编写下落的函数
this._drop(target);
}
其中,函数_drop(target)也是在父组件goods.vue中的methods属性中定义的函数,他主要负责获取到子组件shopCart.vue子组件对象,然后调用该对象里面定义的方法drop(el),将获取到的target传递给el,其中drop(el)是在shopCart.vue中定义的方法
//小球下落的动作
_drop(target){
//体验优化,异步执行下落动作
this.$nextTick(()=>{
//调用 shopcart,给这个组件定义一个小球下落的函数
//然后在shopCart.vue文件里面定义drop()函数,描写一个小球下落的动作
//通过this.$refs.shopcart拿到子组件,然后就可以调用子组件里面定义的方法
this.$refs.shopcart.drop(target); //调用子组件shopCart.vue中定义的方法
});
}
下面看一下shopCart.vue 中的书写
下面这张图是在shopCart.vue中定义小球的html
完整的<template>如下所示
<template>
<div class="shopCart">
<div class="content">
<div class="content-left">
<div class="logo-wrapper">
<!--totalCount在computed中定义,购物车里面的数量totalCount大于0,则-->
<!--购物车背景以及logo呈高亮状态-->
<div class="logo" :class="{'highlight':totalCount>0}">
<span class="icon-shopping_cart" :class="{'highlight':totalCount>0}"></span>
</div>
<!--logo右上角的数字,当totalCount<=0,则不显示-->
<div class="num" v-show="totalCount>0">{{totalCount}}</div>
</div>
<!--totalPrice在computed里面计算,totalPrice>0则价格总数高亮-->
<div class="price" :class="{'highlight':totalPrice>0}">¥{{totalPrice}}</div>
<div class="desc">另需配送费¥{{deliveryPrice}}元</div>
</div>
<div class="content-right">
<!--payClass在computed里面定义,当价格总数>最小价格的时候,最右边的背景呈高亮状态-->
<div class="pay" :class="payClass">
{{payDesc}}
</div>
</div>
</div>
<!--定义小小球-->
<div class="ball-container">
<div v-for="ball in balls">
<transition name="drop" @before-enter="beforeDrop" @enter="dropping" @after-enter="afterDrop">
<div class="ball" v-show="ball.show">
<!--.inner是一个小球-->
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
</div>
</div>
</template>
这里用到了vue中的内置组件<transition>,用来描述缓动动画的
在<script>标签的里面的data()需要定义个数组balls表示存放的每一个小球的状态,这里存放的是5个小球的状态,然后还需要定义一个存放下落小球的数组dropBalls,如下所示
data(){
return {
//维护每个小球的状态,因为transition只对v-if v-show v-for有过渡效果
//所以这里定义了show,在<template>中采用v-show指令
balls:[
{
show: false
},
{
show:false
},
{
show:false
},
{
show:false
},
{
show:false
}
],
dropBalls:[] //存放下落的小球
};
}
然后,需要在methods属性里面定义函数drop(el),主要负责对存放小球状态的数组进行遍历,将show为false的小球的状态改为true,并将它放进dropBalls数组中
drop(el){
//对球进行遍历
let len = this.balls.length;
for( let i=0;i<len;i++ ){
let ball = this.balls[i];
if( !ball.show ){
ball.show = true;
ball.el = el;
this.dropBalls.push(ball); //将下落的小球放进来
return;
}
}
}
然后利用<transition>组件的事件before-enter、enter、after-enter,去描写小球下落的状态,绑定的事件是在methods中定义的
<!--定义小小球-->
<div class="ball-container">
<div v-for="ball in balls">
<transition name="drop" @before-enter="beforeDrop" @enter="dropping" @after-enter="afterDrop">
<div class="ball" v-show="ball.show">
<!--.inner是一个小球-->
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
</div>
beforeDrop(el){
//将所有设为true的小球找到
// 遍历所有的小球
let count = this.balls.length;
while(count--){
let ball = this.balls[count];
if(ball.show){
//返回元素的大小及其相对于视口的位置的对象
let rect = ball.el.getBoundingClientRect();
//水平和竖直方向的偏移量
let x = rect.left - 32; //左下角购物车和右侧点击的“+”的水平距离
let y = -(window.innerHeight - rect.top -22);//竖直方向的距离差
//外层做一个纵向的变化
el.style.display = '';
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)`;
}
}
},
dropping(el,done){
let rf = el.offsetHeight;
this.$nextTick(()=>{
//当下降的时候,重写外部和内部的translate3d()
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){
//当降落完一个ball,就将该ball从balls数组中取出来
let ball = this.dropBalls.shift();
if(ball){
//又可以利用该ball
ball.show = false;
el.style.display = 'none';
}
}
shopCart.vue中的<style>中的代码如下
<style lang="stylus">
.shopCart
position: fixed
left:0
bottom:0
height:48px
z-index:50
width: 100%
.content
display:flex
background: #141d27
font-size:0
color:rgba(255,255,255,0.4)
.content-left
flex:1
.logo-wrapper,.price,.desc
display: inline-block
vertical-align: top
.logo-wrapper
position: relative
top: -10px
margin:0 12px
padding:6px
width:56px
height:56px
box-sizing:border-box
border-radius: 50%
background: #141d27
.logo
width: 100%
height: 100%
border-radius: 50%
background: #2b343c
text-align:center
&.highlight
background:rgb(0,160,220)
.icon-shopping_cart
font-size:24px
line-height:44px
color:#80858a
&.highlight
color:#fff
.num
position: absolute
top:0
right:0
width:24px
height:16px
line-height:16px
text-align:center
border-radius: 16px
font-size:9px
font-weight:700
color: #ffffff
background:rgb(240,20,20)
box-shadow:0 4px 8px 0 rgba(0,0,0,0.4)
.price,.desc
line-height:24px
.price
margin-top:12px
padding-right:12px
box-sizing:border-box
border-right:1px solid rgba(255,255,255,0.1)
font-size:16px
font-weight:700
&.highlight
color:#fff
.desc
margin:12px 0 0 12px
font-size:10px
.content-right
flex:0 0 105px
width:105px
.pay
height:48px
line-height:48px
text-align:center
font-size:12px
font-weight:700
background:#2b333b
&.not-enough
background:#2b333b
&.enough
background: #00b43c
color:#fff
.ball-container
.ball
position:fixed
left:32px
bottom:22px
z-index:200
transition: all 0.4s 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
</style>
以上大概就是实现小球做抛物线的动作的过程