最近上线了一个动画效果偏多的小页面,因为上线时间紧张,来不及作优化,可是其中也不乏有一些功能点能够小小的记录一下。
要达到的效果以下图,须要在必定时间内不断向上拖拽图中的圆(圆会不断放大到某个倍数),同时,页面长背景同步以某个和谐的速率下移,拖拽到页面上方正方形的底部,作碰撞检测。css
开发的过程当中遇到一些难点:css3
一开始评估的时候,其实没往拖拽这个重点想,尽往游戏框架用canvas画去靠拢,由于动效不少,也涉及到碰撞检测等等的。原本想快速的看看游戏框架,并在脑子里看是否match这个项目的需求,但最后仍是放弃了。怕遇到短期会使人抓狂的坑。最后仍是决定就用原生的touch事件写,这样出了bug也好解决一点。
固然,其实这种决策过程也能暴露不少的问题吧,说明也有很是多的进步空间。
总而言之,无论解决方案是什么,在时间紧张的状况下,至少兜底的方案得先出来。canvas
向上提拉的过程,涉及到三个部分的同步动效。背景、圆圈、圆圈的放大。效果出来要和谐的话,就须要不断调试。
解决思路一开始想的很简单,无非是计算出来每一次move的时候手指在屏幕上滑动的距离。根据这个距离,除以必定的系数比例,嗯,一开始是晕着调的。
可是原生的touchmove卡顿严重。致使动效一直不理想。touchmove事件彷佛不是一直触发的。
查到其余人说缘由是由于浏览器
200ms超时致使内核不必定会一直处理touchmove事件,一旦超时会将后续全部的事件转交给UI处理,致使touchmove不会一直触发。系统浏览器也存在一样的问题,为了解决开发者须要,建议开发者在touchstart时调用event.preventDefault,这样就能够保证内核会一块儿触发touchmove事件了
可是调用了preventDefault依然会卡顿。网络上也有不少答案说是要加上passive属性,网络
Passive Event Listeners是Chrome提出的一个新的浏览器特性:Web开发者经过一个新的属性passive来告诉浏览器,当前页面内注册的事件监听器内部是否会调用preventDefault函数来阻止事件的默认行为,以便浏览器根据这个信息更好地作出决策来优化页面性能。当属性passive的值为true的时候,表明该监听器内部不会调用preventDefault函数来阻止默认滑动行为,Chrome浏览器称这类型的监听器为被动(passive)监听器。目前Chrome主要利用该特性来优化页面的滑动性能,因此Passive Event Listeners特性当前仅支持mousewheel/touch相关事件。
引用自: https://juejin.im/post/5b28d6...
可是发现效果依然不满意,仍是十分卡顿。框架
叹了口气,最后的解决方案就是,仍是去找了一个轻便的手势库hammerjs,用到了其中的pan相关的事件。dom
function () { // 绑定手势 var hammertime = new window.Hammer(document.querySelector('xx')) hammertime.get('pan').set({direction: window.Hammer.DIRECTION_ALL}) // 要设定方向才会开启垂直方向的移动 hammertime.on('panstart', this.touchStart) // 上拉 hammertime.on('panup', this.touchMove) hammertime.on('panend', this.touchEnd) },
hammerjs却是很方便, 主要用到其中的panup事件,panup的事件对象里,也暴露出了一些本来须要本身计算的参数,这样能够直接拿来使用, 主要使用到y轴的加速度,还有时间差,deltaTime, velocityY:函数
三个动画须要和谐,大体调整的比例是4:11,若是圆的位置要保持在屏幕中间的话post
// 作边界限定,否则会一次增加过快 if (speed > 4) speed = 4 yuanSpeed= speed * 4 //圆的速率 background.bottom -= speed * 11 // 背景的速率 // 总体增大到1.8倍 yuan.scaleVal += speed / 110 // 圆总体增大
为了效果更好,须要加上一个若是touchend了,就圆就往回落必定距离的效果。
一开始的思路是在圆的现有高度的基础上*0.98,乘以某个系数。可是会出现一个问题,当每次提拉的距离小于0.98的时候,会出现往上拽不动的问题。因此,最终解决方法是,将这一次滑动全部的move累加的值记录下来,touchend的时候再减掉这个累加值的一半:性能
this.totalMove+= yuanSpeed toucheEnd: function () { // 当次距离的一半 this.yuanHeight-= this.totalMove / 2 }
因为这个方案涉及不少dom操做,不像canvas那样有xy坐标,方便作碰撞检测。dom操做在计算的过程当中原本就十分消耗性能。
好比遇到一个问题:
圆的背景图片到盒子顶部有必定的距离,圆自己会不断放大,这个空白的距离怎么计算而且适配全部的屏幕呢?
加入1334设计稿上,这个空白的距离是50px,那么如何动态计算呢?
解决的办法也有点笨,但管用吧。就是用圆放大后的高度/原来圆背景图的高度得出的比例 * 50。
var yuanHeight= yuan.getBoundingClientRect().height // getBoundingClientRect能拿到scale后高度。直接用innerHeight不行 var yuanMargin = (yuanHeight / yuanImg.naturalHeight) * 50 // naturalHeight本来的高度
固然整个计算过程,额,有点费劲
// 主要是圆距离屏幕顶部的高度+超出屏幕部分的高度 // 在这个距离的基础上,减掉或者加上一些其余距离 // maxOffsetTop 是初始的屏幕顶部超出部分 // bottom是不断下移的距离 var top = Math.abs(this.maxOffsetTop - this.bottom) - self.boxBottom + yuanMargin + yuan.getBoundingClientRect().top
当碰撞检测成功或者失败的时候都有相关的动画,一开始是直接用css3写,可是会出现奇怪的第一次闪烁的状况。
索性仍是换了一种可能看上去不是十分优雅,可是好用的办法。
animateFn: function () { // 图片张数和动画时间 01~30 1s var time = 33, // 动画时间 / 图片张数 self = this, idx = this.animateIndex % 30 // animateIndex 初始值为0 imgDom img= $('divImg img') setTimeout(function () { // 控制图片显隐 $(img[idx]).removeClass('displaynone').siblings().addClass('displaynone') self.animateIndex++ // 动画1s后结束 if (self.animateIndex <= 30) { // 调用自身 self.animateFn() } else { // 动画结束 } }, time) },
固然,以上的解决方法都十分笨拙,确定有不少高性能的办法能够打磨得更好,可是呢,我想一想,无论如何,是一个思考过程,仍是须要记录一下。