菜鸡第一次写博客,看懂了的同学记得点个小星星哦
这篇文章是我在慕课网上看vue仿造饿了么外卖APP的项目教程中购物车小球飞入动画所得出的总结,老师是采用vue1.0开发,所以有很多的属性和接口在vue2.0版本以上已经舍弃,所以照着老师做也不一定能跑出来,一直报错。
废话不多说,先献上效果图:
那么,首先我们先捋顺一下思路
1.必须实时监听购物控制组件cartcontrol “+” 号相对于视口屏幕的位置
2.在动画开始之前将小球模型移动到 “+” 号的位置,动画执行时掉落到购物车logo处并消失
3.小球是做抛物线运动,我们可以定义两层盒子,其中外层盒子做y轴运动,内层盒子做x轴运动
具体实现:
1.首先要从cartcontrol中把 “+” 号这个DOM对象传递给父组件goods,再从父组件中传递给购物车组件shopcart
在vue2.0中
可以通过this.$emit来向父组件触发一个自定义事件,同时可将参数传递给父组件。
可以通过设置ref,同时通过this.$refs与子组件取得通信
cartcontrol–html
<template>
<div class="cartcontrol">
<transition name="fade">
<div class="cart-decrease icon-remove_circle_outline" @click="decreaseCart" v-show="food.count>0"></div>
</transition>
<div class="cart-count" v-show="food.count>0">{{food.count}}</div>
<div class="cart-add icon-add_circle" @click="addCart"></div>
</div>
</template>
cartcontrol–js
methods: {
addCart(event) {
// 检测事件派发是否来自于better-scroll,
// 不是则return掉,避免PC端点击的时候触发两次点击事件
if (!event._constructed) {
return
}
// 当给一个观测对象添加一个它不存在的属性时,直接赋值是不可以的
// 需要使用Vue.set设置这个属性
if (!this.food.count) {
Vue.set(this.food, 'count', 1)
} else {
this.food.count++
}
// 向父组件触发一个自定义的cart-add事件,并将事件对象传递给父组件
this.$emit('cart-add', event.target)
}
goods–html
<div class="cartcontrol-wrapper">
<!--在父组件监听到子组件触发的cart-add事件-->
<cartcontrol :food="food" @cart-add="handlecartAdd"></cartcontrol>
</div>
<!-- 记得在shopcart组件中添加ref属性-->
<shopcar ref="shopcar" :selectFoods="selectFoods" :deliveryPrice="seller.deliveryPrice" :minPrice="seller.minPrice"></shopcar>
goods–js
methods: {
// 在goods.vue定义_drop方法将cartcontrol传递过来的target对象再传递给shopcar
_drop(target) {
// 使用nextTick优化动画体验
this.$nextTick(() => {
// 通过$ref属性访问shopcar子组件的drop方法
this.$refs.shopcar.drop(target)
})
},
// 点击加号按钮触发事件
handlecartAdd(target) {
this._drop(target) // 调用_drop方法
}
}
shopcart-html(新增小球模型盒)
<div class="ball-container">
<div v-for="(ball,index) in balls" :key="index">
<transition @before-enter="handleBeforeEnter"
@enter="handleEnter"
@after-enter="handleAfterEnter"
name="drop">
<div class="ball" v-show="ball.show"> <!--外层盒子-->
<div class="inner inner-hook"></div> <!--内层盒子-->
</div>
</transition>
</div>
</div>
shopcart-js
data() {
return {
// 使用balls存放5个小球,这些小球的默认状态都是不显示
// 为什么是5个 不是3个 4个
// 这里的意思是在一瞬间可以点多少次'+'号,5个差不多就够了,3个太少了,作为多年的程序员来说瞬间点3下轻而易举
balls: [{show: false}, {show: false}, {show: false}, {show: false}, {show: false}],
dropBalls: [] // 存放掉落的小球
}
},
methods: {
// 当触发drop方法时小球开始掉落
drop(el) {
console.log(this.dropBalls)
for (let i = 0; i < this.balls.length; i++) {
let ball = this.balls[i]
if (!ball.show) { // 当小球显示状态为隐藏时
ball.show = true
ball.el = el // 将cartControl传过来的对象挂载到ball的el属性上
this.dropBalls.push(ball)
return
}
}
},
// beforeEnter在动画运行之前把小球移到到 ‘+’ 号位置,
// 从左下角移动到右上,所以x是正数,y是负数
handleBeforeEnter: function(el) {
let count = this.balls.length
while (count--) {
let ball = this.balls[count]
if (ball.show) {
// getBoundingClientRect()获取小球相对于视窗的位置,屏幕左上角坐标为0,0
let rect = ball.el.getBoundingClientRect()
// 小球x方向位移= 小球距离屏幕左侧的距离-外层盒子距离水平的距离
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)`
}
}
},
// enter
handleEnter: function(el, done) {
/* 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)'
// Vue为了知道过渡的完成,必须设置相应的事件监听器。
// 如果没有这一句那将不会执行handleAfterEnter
el.addEventListener('transitionend', done)
})
},
// AfterEnter
handleAfterEnter: function(el) {
// 完成一次动画就删除一个dropBalls的小球
let ball = this.dropBalls.shift()
if (ball) {
ball.show = false
//如果没有这一句,小球到达终点后过一小段时间后才消失
//具体原因也是搞不清楚,上面也已经false掉了
el.style.display = 'none'
}
}
}
shopcart–stylus
.ball-container
.ball
position: fixed
left: 32px
bottom: 22px
z-index: 999
&.drop-enter-active, &.drop-leave-active
/*贝塞尔曲线,可以上官网设置https://cubic-bezier.com/*/
transition: all .8s cubic-bezier(0.49,-0.49,0.75,0.41)
.inner
width: 16px
height: 16px
border-radius: 50%
background: rgb(0,160,220)
transition: all .8s