封装CSS动画
写在前面:感谢腾讯课堂与妙味课堂的移动端公开课
对于需要设置动画的元素需要提前设置css()样式,这样数据才会被记录起来。
function css(ele, attr, val) { if (/rotate/.test(attr) || /scale/.test(attr) || /skew/.test(attr) || /translate/.test(attr)) { return cssTransform(ele, attr, val); } if (arguments.length == 2) { var val = getComputedStyle(ele)[attr]; if (attr == 'opacity') { val = Math.round(val * 100); } return parseFloat(val) } if (attr == 'opacity') { ele.style.opacity = val / 100; } else { ele.style[attr] = val + 'px' } }
这里需要判断属于transform的值
/* * 设置transform 只能在这里设置 不能在css设置 * ele.transform ={ * rotate: 40, * scale: 100, * skew: * translate * } * */ function cssTransform(ele, attr, val) { ele.transform = ele.transform || {} if (typeof val == "undefined") { //get if (typeof ele.transform[attr] == 'undefined') { //代表没有设置transform 返回默认值 switch (attr) { case 'scale': case 'scaleX': case 'scaleY': case 'scaleZ': ele.transform[attr] = 100; break; default: ele.transform[attr] = 0; break; } } return ele.transform[attr]; } else { //set var transformVal = ''; ele.transform[attr] = val; for (s in ele.transform) { switch (s) { case 'scale': case 'scaleX': case 'scaleY': case 'scaleZ': transformVal += " " + s + "(" + ele.transform[s]/100 + ")"; break; case 'rotate': case 'rotateX': case 'rotateY': case 'rotateZ': case 'skew': case 'skewX': case 'skewY': transformVal += " " + s + "(" + ele.transform[s] + "deg)"; break; default: transformVal += " " + s + "(" + ele.transform[s] + "px)"; break; } } ele.style.WebkitTransform = ele.style.transform = transformVal; } }
通过检测,在合适位置调用MTwen函数
/* * @param el:控制元素 (element) * @param target:想要达成效果的数据 例如:translateY (string) * @param time:历时 (number) * @param type:运动效果 (string) * @param callIn:运动中执行的函数 (fn) * @param callBack:运动结束执行的函数 (fn) * */ function MTween(init) { var t = 0; var b = {}; var c = {}; var d = init.time / 20; for (var s in init.target) { b[s] = css(init.el, s); c[s] = init.target[s] - b[s]; } clearInterval(init.el.timer); init.el.timer = setInterval(function () { t++; if (t > d) { clearInterval(init.el.timer); init.callBack && init.callBack.call(init.el); } else { init.callIn && init.callIn.call(init.el); for (var s in b) { var val = Number((Tween[init.type](t, b[s], c[s], d)).toFixed(2)); css(init.el, s, val) } } },20) }
先上完整的代码
function css(ele, attr, val) { if (/rotate/.test(attr) || /scale/.test(attr) || /skew/.test(attr) || /translate/.test(attr)) { return cssTransform(ele, attr, val); } if (arguments.length == 2) { var val = getComputedStyle(ele)[attr]; if (attr == 'opacity') { val = Math.round(val * 100); } return parseFloat(val) } if (attr == 'opacity') { ele.style.opacity = val / 100; } else { ele.style[attr] = val + 'px' } } /* * @param el:控制元素 (element) * @param target:想要达成效果的数据 例如:translateY (string) * @param time:历时 (number) * @param type:运动效果 (string) * @param callIn:运动中执行的函数 (fn) * @param callBack:运动结束执行的函数 (fn) * */ function MTween(init) { var t = 0; var b = {}; var c = {}; var d = init.time / 20; for (var s in init.target) { b[s] = css(init.el, s); c[s] = init.target[s] - b[s]; } clearInterval(init.el.timer); init.el.timer = setInterval(function () { t++; if (t > d) { clearInterval(init.el.timer); init.callBack && init.callBack.call(init.el); } else { init.callIn && init.callIn.call(init.el); for (var s in b) { var val = Number((Tween[init.type](t, b[s], c[s], d)).toFixed(2)); css(init.el, s, val) } } },20) } /* * 设置transform 只能在这里设置 不能在css设置 * ele.transform ={ * rotate: 40, * scale: 100, * skew: * translate * } * */ function cssTransform(ele, attr, val) { ele.transform = ele.transform || {} if (typeof val == "undefined") { //get if (typeof ele.transform[attr] == 'undefined') { //代表没有设置transform 返回默认值 switch (attr) { case 'scale': case 'scaleX': case 'scaleY': case 'scaleZ': ele.transform[attr] = 100; break; default: ele.transform[attr] = 0; break; } } return ele.transform[attr]; } else { //set var transformVal = ''; ele.transform[attr] = val; for (s in ele.transform) { switch (s) { case 'scale': case 'scaleX': case 'scaleY': case 'scaleZ': transformVal += " " + s + "(" + ele.transform[s]/100 + ")"; break; case 'rotate': case 'rotateX': case 'rotateY': case 'rotateZ': case 'skew': case 'skewX': case 'skewY': transformVal += " " + s + "(" + ele.transform[s] + "deg)"; break; default: transformVal += " " + s + "(" + ele.transform[s] + "px)"; break; } } ele.style.WebkitTransform = ele.style.transform = transformVal; } } /* * t : time 已过时间 * b : begin 起始值 * c : count 总的运动值 * d : duration 持续时间 * */ //Tween.linear(); var Tween = { linear: function (t, b, c, d) { //匀速 return c * t / d + b; }, easeIn: function (t, b, c, d) { //加速曲线 return c * (t /= d) * t + b; }, easeOut: function (t, b, c, d) { //减速曲线 return -c * (t /= d) * (t - 2) + b; }, easeBoth: function (t, b, c, d) { //加速减速曲线 if ((t /= d / 2) < 1) { return c / 2 * t * t + b; } return -c / 2 * ((--t) * (t - 2) - 1) + b; }, easeInStrong: function (t, b, c, d) { //加加速曲线 return c * (t /= d) * t * t * t + b; }, easeOutStrong: function (t, b, c, d) { //减减速曲线 return -c * ((t = t / d - 1) * t * t * t - 1) + b; }, easeBothStrong: function (t, b, c, d) { //加加速减减速曲线 if ((t /= d / 2) < 1) { return c / 2 * t * t * t * t + b; } return -c / 2 * ((t -= 2) * t * t * t - 2) + b; }, elasticIn: function (t, b, c, d, a, p) { //正弦衰减曲线(弹动渐入) if (t === 0) { return b; } if ((t /= d) == 1) { return b + c; } if (!p) { p = d * 0.3; } if (!a || a < Math.abs(c)) { a = c; var s = p / 4; } else { var s = p / (2 * Math.PI) * Math.asin(c / a); } return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; }, elasticOut: function (t, b, c, d, a, p) { //正弦增强曲线(弹动渐出) if (t === 0) { return b; } if ((t /= d) == 1) { return b + c; } if (!p) { p = d * 0.3; } if (!a || a < Math.abs(c)) { a = c; var s = p / 4; } else { var s = p / (2 * Math.PI) * Math.asin(c / a); } return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b; }, elasticBoth: function (t, b, c, d, a, p) { if (t === 0) { return b; } if ((t /= d / 2) == 2) { return b + c; } if (!p) { p = d * (0.3 * 1.5); } if (!a || a < Math.abs(c)) { a = c; var s = p / 4; } else { var s = p / (2 * Math.PI) * Math.asin(c / a); } if (t < 1) { return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b; } return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b; }, backIn: function (t, b, c, d, s) { //回退加速(回退渐入) if (typeof s == 'undefined') { s = 1.70158; } return c * (t /= d) * t * ((s + 1) * t - s) + b; }, backOut: function (t, b, c, d, s) { if (typeof s == 'undefined') { s = 3.70158; //回缩的距离 } return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b; }, backBoth: function (t, b, c, d, s) { if (typeof s == 'undefined') { s = 1.70158; } if ((t /= d / 2 ) < 1) { return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b; } return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b; }, bounceIn: function (t, b, c, d) { //弹球减振(弹球渐出) return c - Tween['bounceOut'](d - t, 0, c, d) + b; }, bounceOut: function (t, b, c, d) { if ((t /= d) < (1 / 2.75)) { return c * (7.5625 * t * t) + b; } else if (t < (2 / 2.75)) { return c * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75) + b; } else if (t < (2.5 / 2.75)) { return c * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375) + b; } return c * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375) + b; }, bounceBoth: function (t, b, c, d) { if (t < d / 2) { return Tween['bounceIn'](t * 2, 0, c, d) * 0.5 + b; } return Tween['bounceOut'](t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b; } }
接下来说一下使用方式
1.设置css(el,'translateY',) //获取translateY的值
2.处理后 css(el, 'translateY', -translateY*scale)//获设置translateY的值
3.运行动画
MTween({ el: el, target: { translateY: target }, time:3, type: 'backOut', callBack: function () { (init.offBar)|| (scrollBar.style.opacity = 0); }, callIn: function () { var translateY = css(inner, 'translateY'); (init.offBar)|| css(scrollBar, 'translateY', -translateY*scale); } })
接下来的完整的代码
可以自己去尝试下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0"> <title>Title</title> <style> body { margin: 0; } body, html { height: 100%; overflow: hidden; position: relative; } header { height: 40px; font-size: 20px; line-height: 40px; text-align: center; background: #000; color: #fff; width: 100%; } footer { height: 40px; line-height: 40px; text-align: center; position: absolute; bottom: 0; left: 0; right: 0; background: #000; color: white; } #wrap { position: absolute; left: 0; right: 0; bottom: 40px; top: 40px; overflow: hidden; } #list { margin: 0; padding: 0; list-style: none; } #list li { height: 30px; border-bottom: 1px solid #000; line-height: 30px; text-indent: 20px; font-size: 16px; } </style> </head> <body> <header id="header">测试滑屏动画</header> <section id="wrap"> <ul id="list"></ul> </section> <footer id="footer">慢慢的干活,还是很不错</footer> <script src="m.Tween.js"></script> <script> /* * 缓冲动画 * 获取最后一次速度=位移/时间 * * */ document.addEventListener('touchstart', function (ev) { ev.preventDefault(); }) function setListInner() { var list = document.querySelector('#list'); var inner = ''; for (var i = 0; i < 100; i++) { inner += '<li>这是第' + i + '个li</li>' } list.innerHTML = inner } window.onload = function () { setListInner(); var wrap = document.querySelector('#wrap'); mScroll({ el: wrap, offBar : false, start : function(e){ }, move : function(e){ }, end:function(e){ }, over:function(e){ } }) }; //添加父级 function mScroll(init){ if(!init.el){ return; } var inner = init.el.children[0]; var scrollBar = document.createElement('div'); var startPoint = 0; var startEl = 0; var lastDis = 0; //最后一次距离 var lastY = 0; //上一次距离 var lastTime = 0; var lastTimeDis = 0; var maxTranslate = init.el.clientHeight - inner.offsetHeight; var scale = init.el.clientHeight / inner.offsetHeight; inner.style.minHeight = '100%'; if(!init.offBar){ scrollBar.style.cssText = 'width:6px;background:rgba(0,0,0,.5);' + 'position:absolute;right:0;top:0;border-radius:3px;opacity:0;transition:opacity .3s'; init.el.appendChild(scrollBar); } //设置动画 css(inner, 'translateZ', 0.01); inner.addEventListener('touchstart', function (e) { clearInterval(inner.time); //预留后面高度被改变 maxTranslate = init.el.clientHeight - inner.offsetHeight; //内容太少 if(!init.offBar){ if(maxTranslate >= 0){ scrollBar.style.display = 'none' }else{ scrollBar.style.display = 'block' } scale = init.el.clientHeight / inner.offsetHeight; scrollBar.style.height = init.el.clientHeight * scale+'px'; } startPoint = e.changedTouches[0].pageY; startEl = css(inner, 'translateY'); //获得点击下去的translateY 的值 lastY = startEl; //赋值每一次开始的位置 lastTimeDis = lastDis = 0; lastTime = new Date().getTime(); (init.offBar)||(scrollBar.style.opacity = 1); }); inner.addEventListener('touchmove', function (e) { var nowPoint = e.changedTouches[0].pageY; var dis = nowPoint - startPoint; var translateY = dis + startEl; var nowTime = new Date().getTime(); css(inner, 'translateY', translateY); (init.offBar)||css(scrollBar, 'translateY', -translateY*scale); lastDis = translateY - lastY; lastY = translateY; lastTimeDis = nowTime - lastTime; lastTime = nowTime; }); inner.addEventListener('touchend', function (e) { var type = 'easeOut'; var speed = Math.round(lastDis / lastTimeDis * 10); speed = lastTimeDis <= 0 ? 0 : speed; var target = Math.round(speed * 30 + css(inner, 'translateY'));//缓冲距离 if (target > 0) { target = 0; type = 'backOut' } else if (target < maxTranslate) { target = maxTranslate; type = 'backOut' } MTween({ el: inner, target: { translateY: target }, time: Math.round(Math.abs(target - css(inner, 'translateY')) * 1.5), type: type, callBack: function () { (init.offBar)|| (scrollBar.style.opacity = 0); }, callIn: function () { var translateY = css(inner, 'translateY'); (init.offBar)|| css(scrollBar, 'translateY', -translateY*scale); } }) }) } </script> </body> </html>