这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战
商品飞入购物车动画,是购物类 APP 常见的效果,比如美团外卖、饿了么外卖等,飞入的动画效果可以有多种方式来实现。
1、技术方案
第一种方案,基于 css3 有两种方法可以实现同样的效果:
- 利用 animation 动画
- 利用 transform 和 transition
第二种方案,javascript 中也有运动函数的概念:
-
小伙伴可以参考这篇文章
-
张鑫旭大神有一篇博客,讲的也是 javascript 抛物线运动,写的非常详细:
第三种方案,vue 中,提供了用于过渡的组件:transition
一般说来,在 vue 中我更倾向于结合 css3 来实现动画效果。
2、直线运动
transform 和 transition ,只能控制一个物体做直线运动。比如下面的代码:
<style>
#ball{
width: 10px;
height: 10px;
border-radius: 50%;
background: red;
transition: all 1s linear;
}
</style>
<body>
<div id="ball"></div>
</body>
<script>
setTimeout(function(){
ball.style.transform= "translate(800px, 500px)";
},1000)
</script>
这段代码的效果是让一个小球从坐标[0,0]飞向坐标[800,500],飞行的时间为 1s
如果我们想要实现一条类似抛物线的路径,得先来认识一下贝塞尔曲线了。
3、贝塞尔曲线(cubic-bezier)
这里有一篇关于贝塞尔曲线的博客,讲解的比较深刻:点我
当然了,单纯的从数学角度来理解贝塞尔曲线,有点大材小用。这是一个 CSS3 贝塞尔曲线工具、CSS3 贝塞尔曲线模板网站,可以用于调试曲线
当然啦,其实你在以前可能已经接触过贝塞尔曲线了,如果你用过 jquery 的动画的话。
在 jquery 动画中设置的 timing-function,如 easy、easy-in、easy-in-out 等,其实就是写好的贝塞尔曲线。
本质上,贝塞尔曲线是用来控制动画速度的,简单来说,就是一段变化是匀速的?还是先快后慢?或者先慢后快呢?
它而并非是小球所走过的路径,实际上,即便使用了贝塞尔,一个小球的飞行轨迹依然是一条直线。
这是因为,物体的运动永远受到 x 轴和 y 轴的作用,而当两个轴上的运动模型受到同一个 transition 的控制,二者总会合并成一个固定的方向。
就好像中学物理中力的合成。
大家可以在这里贝塞尔曲线看到相关的效果,我就不再赘述。
根据上面的说法简单的总结下:
想让物体曲线运动,我们可以创建两个物体,一个负责 x轴上的动画,一个负责 y轴的动画,并且,动画的时长要完全一致,而 timing-function 不同步,这样两个物体的运动互相影响,就会形成曲线运动轨迹。
4、代码
我们来简单的做个案例。
首先,我们需要布局两个物体,一个在 x 轴运动,另一个则在 y 轴上运动。
<template>
<div class="home">
<div class="cart">cart</div>
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<div class="ball" v-show="ball.show">
<div class="innerBall">
<i class="iconfont icon-xingxing"></i>
</div>
</div>
</transition>
<div class="menu" @click="addToCart($event)">menu</div>
</div>
</template>
这里我们写了三个东西,分别是
- 购物车 cart
- 一个用于点击的按钮 menu
- 一个小球,这个小球由两个 div 构成,一个是外层的 ball,另外一个 div 在 ball 的内部,叫做 innerBall,并且使用了的 vue 的内置组件
transition
接下来我们要定义一些简单的 css 样式。
<style lang="scss">
.cart,
.ball {
position: fixed; right: 700px; top: 100px;
}
.cart {
border: 1px solid blue; padding: 5px 12px;
}
.ball {
width: 10px; height: 10px; transition: all 1s cubic-bezier(0.49, -0.29, 1, 1);
}
.innerBall {
width: 10px; height: 10px; transition: all 1s linear;
}
.menu {
position: absolute; top: 500px; left: 50px; border: 1px solid blue;
padding: 5px 12px; cursor: pointer;
}
</style>
针对 ball 和 innerBall,我们使用 css3 的 transition添加上了过渡的效果,其中我们让 ball 的运动模型为贝塞尔曲线 cubic-bezier(0.49, -0.29, 1, 1),innerBall 的运动模型为直线运动 linear
vue 的 transition 组件,可以定义过渡期间的动画效果,这里的定义既可以使用 css 的类名,也能使用绑定事件的方式
其中,css 类名总共有6个,分别如下:
但这里我并不打算使用类名的方式,而是采用 js 事件,这里我先简单的陈述一下事件逻辑:
事件分为动画开始前,动画中和动画结束后,针对的绑定事件的名称就是before-enter,enter,after-enter
其中在动画开始前,我们需要:
- 获取 ball 和 innerBall
- 计算小球的位置到购物车的位置的距离 x、y
- 重置小球的位置为初始位置
在动画执行阶段,我们需要:
- 让 ball 沿着 x 轴运动
- 让 innerBall 沿着 y 轴运动
- 运动通过 transform: translate 来实现
在动画技术后,我们将小球隐藏,这样在动画开始之前重制小球的位置时,用户是不会看到的。
下面是具体的代码,我已经做好了注释
<script>
export default {
name: 'home',
data() {
return {
ball: {
el: null,
show: false,
},
}
},
methods: {
beforeEnter(el) {
let ele = this.ball.el //要添加购物车的商品
let ract = ele.getBoundingClientRect() //商品的位置
let elRight = this._getStyle(el, 'right') //购物车 right
let elTop = this._getStyle(el, 'top') //购物车 top
let x = window.innerWidth - ract.left - parseFloat(elRight) // 计算小球移动的X轴的距离
let y = ract.top - parseFloat(elTop)// 计算小球移动的y轴的距离
el.style.display = ''
el.style.transform = `translateY(${y}px)` //重置小球的x轴位置
let innerBall = el.querySelector('.innerBall')
innerBall.style.transform = `translateX(-${x}px)`//重置小球的y轴位置
},
enter(el, done) {
this._offset = document.body.offsetHeight //激发重绘
el.style.transform = `translate(0, 0)` //小球沿着y轴移动到购物车
let innerBall = el.querySelector('.innerBall')
innerBall.style.transform = `translate(0, 0)`//小球沿着x轴移动到购物车
el.addEventListener('transitionend', done)
},
afterEnter(el) {
this.ball.show = false
el.style.display = 'none'
},
addToCart(event) {
this.ball.el = event.target
this.ball.show = true
},
_getStyle(el, attr) {
return el.currentStyle
? el.currentStyle[attr]
: getComputedStyle(el, false)[attr]
},
},
}
</script>
写好以后,我们启动本地服务在浏览器中看看效果吧: