前言
- 主要涉及vue中的列表过渡transition的钩子
- 小球的掉落购物车的轨道即 曲线运动——贝塞尔曲线可以根据手动规划小球运动路径给transition: all 0.7s cubic-bezier(0.49, -0.29, 0.75, 0.41);的动画过渡效果赋值,到达效果
实现代码
ball.vue组件
<template>
<view class="demo">
<!-- <view class="addBtn" @click="addGoods"></view> -->
<!-- <view class="cart">
这是购物车
</view> -->
<view v-for="(ball, index) of balls" :key="index">
<transition
name="drop"
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<view class="ball" v-show="ball.show">
<!--这里为了做两个维度的动画,因此需要多包一层,外层做Y轴,内层做X轴动画-->
<view class="inner inner-hook" >+</view>
</view>
</transition>
</view>
</view>
</template>
<script>
export default {
data() {
return {
balls: [
// 这里定义了多个ball,是因为可能同时有多个小球在动画中(快速点击多次或者多个商品)
{
show: false
},
{
show: false
},
{
show: false
},
{
show: false
},
{
show: false
}
],
dropBalls: [] // 在动画中的小球集合
};
},
methods: {
addGoods(e) {
let el = e.target;
this.balls.forEach(v => {
if (!v.show) {
v.show = true; // 当切换元素的display:block/none时,会触发vue的动画
v.el = el; // 将触发点击事件的“+”号保定道小球对象上,方便获取动画初始时的位置
this.dropBalls.push(v); // 取一个小球加入动画队列
return;
}
});
},
beforeEnter(el) {
let count = this.dropBalls.length;
while (count--) {
// 将动画队列中的小球,依次处理
let ball = this.dropBalls[count];
// console.log(ball.el)
if (ball.show) {
// let rect = ball.el;
let rect = ball.el.getBoundingClientRect(); //拿到点击的“+”号的位置,这里不直接取值(我是用的绝对定位,当然可以直接取值)的原因是,商品列表中每个加号的位置是不固定的,如果上下滑动了,这个位置就不确定
let x = rect.offsetLeft - 45; // 需要偏移的x向距离
let y = -(window.innerHeight - rect.offsetTop - 60); // 需要偏移的y向距离
el.style.display = ""; // 当前状态下,display值为none,将其置空。
// 这里需要注意了,小球飞入的动画分两个维度,X轴和Y轴,因此
el.style.webkitTransform = `translate3d(0px, ${y}px, 0px)`; // 首先将“ball”Y向移动至“+”好位置
el.style.transform = `translate3d(0px, ${y}px, 0px)`;
// 接着将“inner-hook”X向移动至“+”号处,其实此时外层“ball”的X位置没有动,但因为背景色等等样式只应用于“inner-hook”上,因此,视觉效果上,这个小球是移动到了“+”号的位置
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) {
/* eslint-disable no-unused-vars */
let rf = el.offsetHeight; // 主动触发浏览器重绘
this.$nextTick(() => {
el.style.webkitTransform = "translate3d(0, 0, 0)"; //接着将小球位置置为初始值,但css中设置了transition .8s,因此,动画效果就出来了
el.style.transform = "translate3d(0, 0, 0)";
let inner = el.getElementsByClassName("inner-hook")[0];
inner.style.webkitTransform = "translate3d(0, 0, 0)";
el.style.transform = "translate3d(0, 0, 0)";
});
},
afterEnter(el) {
let ball = this.dropBalls.shift(); //结束后,将这个活动中的小球删除
if (ball) {
ball.show = false;
el.style.display = "none"; // 并且将其设为不可见
}
}
}
};
</script>
<style lang="scss" scoped>
.demo{
.cart {
position: absolute;
left: 30px;
bottom: 30px;
width: 100px;
text-align: center;
height: 50px;
line-height: 50px;
border: 1px solid #ccc;
}
.addBtn{
position: absolute;
top: 100px;
right: 50px;
width: 20px;
height: 20px;
background: blue;
border-radius: 50%;
}
.ball{
position: fixed;
z-index: 9999;
left: 40px;
bottom: 45px;
&.drop-enter-active{
transition: all 0.7s cubic-bezier(0.49, -0.29, 0.75, 0.41);
}
.inner{
width: 15px;
height: 15px;
border-radius: 50%;
background: #FF4343;
text-align: center;
font-size: 11px;
line-height: 12px;
color: white;
transition: all 0.8s;
}
}
}
</style>
调用ball.vue组件的父组件
<template>
<view class="main">
<ball ref="addGoods"></ball>
</view>
</template>
<script>
import ball from '../ball/ball'
export default {
components: {
ball
},
method: {
// 购物车添加
handleAddCar(item, $event) {
// **$event**是指当前触发的是什么事件(鼠标事件,键盘事件等)
// $event.target**则指的是事件触发的目标,即哪一个元素触发了事件,这将直接获取该dom元素
// 动态获取到(每个商品加入按钮的位置信息)元素的获取其属性值;并传入到ball组件中
this.$refs.addGoods.addGoods($event)
console.log($event)
}
}
</script>
<style lang="scss">
@mixin img-width-height($width, $height) {
height: $height;
width: $width;
object-fit: cover;
}
.footer-img{
top: -20px;
position: absolute;
border-radius: 50%;
border: 5px solid white;
@include img-width-height(61px, 61px);
}
</style>
详解
-
生成一个动画小球的div,并且生成五个小球,五个是为了生成一定数量的小球来作为操作使用,按照小球动画的速度,一般来说五个也可以保证有足够的小球数量来运行动画
-
动画的内容分别是外层和内层,外层控制动画小球的轨道和方向,内层控制动画小球的运行状态
-
动画使用vue的js钩子实现
-
因为小球动画只有一个方向(只执行单方向从上到下滚落),所以只用了before-enter,enter,after-enter
-
用v-show控制小球的可见性,在动画执行期间可见,其余时候隐藏
-
只要触发了drop事件,不止是drop事件里面的代码会执行,另外几个vue的js监听钩子也会一起按顺序执行
- 触发了drop事件
- beforeEnter开始执行
- enter开始执行
- afterEnter开始执行
-
drop事件的触发可以通过点击cartcontrol组件的添加小球按钮addCart事件触发使用
$emit,也可以父组件this.$refs.shopcart.drop(target);直接触发- 这么做的目的是实现,在子组件cartcontrol点击之后,可以将该dom传给父组件goods然后再传给子组件shopcart,(因为目前他们之间的通道就是这样,shopcart子组件并没有导入cartcontrol子组件,所以没有直接通讯)这样就实现了多个组件之间的通讯,从而可以实现需求,例如这里就是实现点击子组件cartcontrol后添加一个动画,将小球滑落到另外一个组件shopcart
-
$emit是触发当前实例上的事件。附加参数都会传给监听器回调。
-
关于drop方法,是实现每一个ball的show属性和el属性处理,并且点击一次会自动将一个小球放到dropBalls数组里面,放到里面就代表的是一个小球已经被开始执行动画,但是由于动画是异步的,所以先主动设置.
-
关于getBoundingClientRect(位移的计算是从左上角开始)
- 使用getBoundingClientRect获取到当前元素的坐标,然后需要位移的left减去元素的宽获取真正的最终位移x坐标
- 使用getBoundingClientRect获取到当前元素的坐标,然后需要当前屏幕的高度减去元素的top再减去元素本身的高度获取到真正的最终位移y坐标,并且这个是负数,因为是从左上角往下的方向
-
关于html重绘
- 因为浏览器对于重绘是有要求并且是有队列完成的,这是主要为了性能,虽然动画隐藏了小球
display none,但没有触发html重绘,或者说没有立即触发html重绘,所以需要手动 let rf = el.offsetHeight;这是一个手动触发html重绘的方法- 网页性能管理详解 http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html
- 高性能JavaScript 重排与重绘http://www.cnblogs.com/zichi/p/4720000.html
- 因为浏览器对于重绘是有要求并且是有队列完成的,这是主要为了性能,虽然动画隐藏了小球
animation的动画
第一、分解运动
上图的曲线运动进行分解
向右:匀速运动;
向上:加速运动;(因为向上的线越来越陡,意味着速度越来越快,所以在做加速运动)
第二、实现代码解释
有了对该运动的本质认识,那么实现起来就很易如反掌了;
**
@keyframes run-right-right {
0% {
left: 40px;
transform: scale(0.7);
}
100% {
left: 600px;
transform: scale(0.45);
}
}
——@keyframes 是css3的一个规则,用来定义一个运动的每个桢的位置,大小颜色等;
然后run-right-right就是这个动画特性的名字,很好理解;然后0%就是开始的时候的left值;transform: scale(0.7);变小0.7;时间到100%时 left值变大到600px的位置,然后变小到0.45;(这里的位置,大小都是相对于初始设置的ball类里面的值)
**
animation: run-right-right 3s 0.4s 1 linear, run-right-top 3s 0.4s 1 cubic-bezier(.66,.1,1,.41);
——这段代码的意思是动画animation用run-right-right动画和 run-right-top动画,注意我们是同时引用的两个动画;
就是@keyframes 所定义的,然后我们又设置了一些参数,逐一解释:
- 一.第一个参数就是引用的动画名字;
- 二,动画持续时间3s;
- 三、0.4s是延迟时间为0.4s,以run_top_right加到ball上面的时间为准,延后0.4s;
- 四、1是动画的执行次数是1次;
- 五、cubic-bezier(.66,.1,1,.41) 就是重要的贝塞尔曲线(cubic-bezier) ;
实际上是设置animation-timing-function的属性;就是设置运动速度的特性属性;
-
- linear,就是线性运动,也就是匀速运动;
- 2 ease,默认。动画以低速开始,然后加快,在结束前变慢。
- 3.ease-in,ease-out,ease-in-out,就很好记,ease就是慢的意思,ease-in就是慢速开始,就是做加速运动,ease-out就是减速运动,ease-in-out就是先加速后减速;当然我们也可以设置成cubic-bezier()值;