生成一个动画小球的div,而且生成五个小球,五个是为了生成必定数量的小球来做为操做使用,按照小球动画的速度,通常来讲五个也能够保证有足够的小球数量来运行动画css
动画的内容分别是外层和内层,外层控制动画小球的轨道和方向,内层控制动画小球的运行状态html
动画使用vue的js钩子实现vue
由于小球动画只有一个方向(只执行单方向从上到下滚落),因此只用了before-enter,enter,after-entercss3
用v-show控制小球的可见性,在动画执行期间可见,其他时候隐藏web
<div class="ball-container"> <div v-for="ball in balls"> //用了两种方式的动画,css和js钩子 <transition name="drop" @before-enter="beforeDrop" @enter="dropping" @after-enter="afterDrop"> //外层动画 <div class="ball" v-show="ball.show"> //内层动画 <div class="inner inner-hook"></div> </div> </transition> </div> </div>
设置了balls数组来表明五个小球canvas
设置了dropBalls数组正在运行的小球api
data(){ return { balls: [ {show: false}, {show: false}, {show: false}, {show: false}, {show: false} ], dropBalls: [] } },
只要触发了drop事件,不止是drop事件里面的代码会执行,另外几个vue的js监听钩子也会一块儿按顺序执行数组
触发了drop事件浏览器
beforeDrop开始执行dom
dropping开始执行
afterDrop开始执行
drop事件的触发能够经过点击cartcontrol组件的添加小球按钮addCart事件触发使用$emit
,也能够父组件 this.$refs.shopcart.drop(target);
直接触发
这么作的目的是实现,在子组件cartcontrol点击以后,能够将该dom传给父组件goods而后再传给子组件shopcart,(由于目前他们之间的通道就是这样,shopcart子组件并无导入cartcontrol子组件,因此没有直接通信)这样就实现了多个组件之间的通信,从而能够实现需求,例如这里就是实现点击子组件cartcontrol后添加一个动画,将小球滑落到另一个组件shopcart
$emit是触发当前实例上的事件。附加参数都会传给监听器回调。
methods: { drop(el) { //触发一次事件就会将全部小球进行遍历 for (let i = 0; i < this.balls.length; i++) { let ball = this.balls[i]; if (!ball.show) { //将false的小球放到dropBalls ball.show = true; ball.el = el; //设置小球的el属性为一个dom对象 this.dropBalls.push(ball); return; } } }, beforeDrop(el){ //这个方法的执行是由于这是一个vue的监听事件 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 = ''; //清空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-hook类来单纯被js操做 inner.style.webkitTransform = `translate3d(${x}px,0,0)`; inner.style.transform = `translate3d(${x}px,0,0)`; } } }, dropping(el, done) { //这个方法的执行是由于这是一个vue的监听事件 /* eslint-disable no-unused-vars */ let rf = el.offsetHeight; //触发重绘html this.$nextTick(() => { //让动画效果异步执行,提升性能 el.style.webkitTransform = 'translate3d(0,0,0)'; el.style.transform = 'translate3d(0,0,0)'; //处理内层动画 let inner = el.getElementsByClassName('inner-hook')[0]; //使用inner-hook类来单纯被js操做 inner.style.webkitTransform = 'translate3d(0,0,0)'; inner.style.transform = 'translate3d(0,0,0)'; el.addEventListener('transitionend', done); //Vue为了知道过渡的完成,必须设置相应的事件监听器。 }); }, afterDrop(el) { //这个方法的执行是由于这是一个vue的监听事件 let ball = this.dropBalls.shift(); //完成一次动画就删除一个dropBalls的小球 if (ball) { ball.show = false; el.style.display = 'none'; //隐藏小球 } } }
关于drop方法,是实现每个ball的show属性和el属性处理,而且点击一次会自动将一个小球放到dropBalls数组里面,放到里面就表明的是一个小球已经被开始执行动画,可是因为动画是异步的,因此先主动设置.
关于getBoundingClientRect(位移的计算是从左上角开始)
使用getBoundingClientRect获取到当前元素的坐标,而后须要位移的left减去元素的宽获取真正的最终位移x坐标
使用getBoundingClientRect获取到当前元素的坐标,而后须要当前屏幕的高度减去元素的top再减去元素自己的高度获取到真正的最终位移y坐标,而且这个是负数,由于是从左上角往下的方向
关于html重绘
由于浏览器对于重绘是有要求而且是有队列完成的,这是主要为了性能,虽然动画隐藏了小球display none
,但没有触发html重绘,或者说没有当即触发html重绘,因此须要手动
let rf = el.offsetHeight;
这是一个手动触发html重绘的方法
.ball-container .ball position: fixed //小球动画必须脱离html布局流 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
关于cubic-bezier(0.49, -0.29, 0.75, 0.41)
,是动画抛物曲线(贝塞尔曲线)的配置,基于css3实现,http://cubic-bezier.com/#.17,.67,.83,.67,参考贝塞尔曲线与CSS3动画、SVG和canvas的基情 ,至于抛物线放在外层就是为了控制内层的元素的轨道和方向的.