移动端 transition动画函数的封装(仿Zepto)以及 requestAnimationFrame动画函数封装(仿jQuery)
移动端 css3 transition 动画 ,requestAnimationFrame 动画 对于性能的要求,h5优先考虑;
移动端 单页有时候 制作只用到简单的css3动画即可,我们封装一下,没必要引入zepto框架,把zepto的动画模块从Zepto 扒下来,加以改造独立;用于生产环境;下面是 Demo栗子;
上面图片对应的 js
var leftsbox=document.getElementById("leftsbox"); var boxdiv=leftsbox.getElementsByTagName("div"); leftsbox.onclick=function(){ for(var i=0;i<boxdiv.length;i++){ var that=boxdiv[i]; transform(that,{ translate3d:'220px,10px,0',left:'1em',opacity:0.2,perspective:'400px', rotateY:'30deg'},800,'cubic-bezier(0.15, 0.5, 0.25, 1.0)',function(){ this.innerHTML='结束回调'+this.innerHTML; },100*i); } //for
再看看另外一种 常见的 如下图
上面对用的 js 代码
var nav=document.querySelector(".nav"); var nava=nav.getElementsByTagName("li"); var content=document.querySelector(".content"); var ulcontent=document.getElementById("ulcontent"); ulcontent.style.width=nav.offsetWidth*nava.length+'px'; for(var i=0;i<nava.length;i++) { nava[i].index=i; nava[i].onclick=function(){ var that=this; var now=-(that.index)*content.offsetWidth+'px'; transform(ulcontent,{translate3d:''+now+',0,0',},'linear',function(){ //console.log('success 回调函数'); }) }//click end }
htm结构
<ul class="nav"> <li ><a >首页</a></li> <li ><a >插件</a></li> <li ><a >新闻</a></li> <li ><a >其他</a></li> </ul> <div class="content"> <ul id="ulcontent" > <li ><img src="../../images/1a.jpg"></li> <li ><img src="../../images/2a.jpg"></li> <li ><img src="../../images/3a.jpg"></li> <li style="background:#ddd;" >44444444444</li> </ul> </div>
基于zepto动画独立出来,原先zepto 动画书写方式
$("#banners").animate( { translate3d:'220px,10px,0', left:'1em', opacity:0.2, perspective:'400px', rotateY:'30deg' }, 800, 'cubic-bezier(0.15, 0.5, 0.25, 1.0)', function(){ alert('回调'); }, 1000 )
改写后 独立与zepto的 动画函数 语法如下
transform(dom元素,{ transitionProperty:css值},transitionDuration(单位:毫秒),transitionTiming,transitionend回调,transitionDelay(单位:毫秒));
transform(dom元素,animationName,animationDuration(单位:毫秒),animationTiming,animationend回调,animationDelay(单位:毫秒));
/* * js transfrom * @param obj {obj} 原生dom对象 * @param properties {json} ||string { translate3d:'220px,10px,0',left:'1em',opacity:0.2, rotateY:'30deg'} || animationName 多个可以以逗号分割 如 'fadeIn,sliderDown'; * @param duration {number} css3持续时间 秒 默认400毫秒 * @param ease {str} 默认linear 支持 cubic-bezier(0.42,0,1,1)写法; * @param callback {function} 回调函数 * @param delay {number} 延迟时间 */
/* http://www.cnblogs.com/surfaces/ * @param properties 为 {} 或者 string ;如果 properties= string 为animation-name * transform(elem, properties) * transform(elem, properties, ease) * transform(elem, properties, ease, delay) * transform(elem, properties, ease, callback, delay) * transform(elem, properties, callback) * transform(elem, properties, callback, delay) * transform(elem, properties, duration ) * transform(elem, properties, duration, ease) * transform(elem, properties, duration, delay) * transform(elem, properties, duration, callback) * transform(elem, properties, duration, callback,delay) * transform(elem, properties, duration, ease, delay) * transform(elem, properties, duration, ease, callback) * transform(elem, properties, duration, ease, callback,delay) transform(elem,{translateX:'150px',left:'1em',opacity:0.2, rotateY:'40deg'},600,'linear',function(){ console.log('结束回调') },200) ; transform(elem, keyframesName,600,'linear',function(){ console.log('结束回调') },200) ; */
关于兼容性:几乎与zepto一致 ,也支持 动画帧 keyframe,个人觉得 keyframe移动端 兼容性不是很好,尤其手机自带的浏览器(原生app 调用 内嵌H5页面,小问题还是蛮多的),适合无关dom数据操作,只显示美化用;
另外,我们需要了解下 css3 transition-duration的属性写法有多种;如下
1). 过渡单个属性:
transition-property:opacity; transition-duration:2s; transition-timing-function:ease-in; transition-delay:0;
2). 过渡多个属性:
[1]. 上下一一对应型:
transition-property:opacity left; transition-duration:2s, 4s; transition-timing-function:ease-in; transition-delay:0;
此时:opacity过渡时间为2s,left过渡时间为4s。
[2]. 循环对应型:
transition-property:opacity left width height; transition-duration:2s, 4s; transition-timing-function:ease-in; transition-delay:0;
此时:opacity和width过渡时间为2s,left和height过渡时间为4s。
3). transition简写模式:
顺序为:transition-property transition-duration transition-timing-function transition-delay
/*单个属性:*/
-moz-transition:background 0.5s ease-out 0s;
/*多个属性:*/
-moz-transition:background, 0.5s ease-out 0s, color 0.4 ease-out 0s;
过渡持续时间
transition-duration 属性规定了一个过渡从初始状态到目标状态的持续时间。它接受以秒或毫秒的值(例如,2.3S和2300ms都是指2.3秒)。
尽管规范明确规定了过渡值必须为正数,但 Opera 仍接受-5S的值,至少对于getComputedStyle()来说是这样的。虽然规范中并没有限制属性值的大小,但 Opera 和 IE 不接受低于10ms的值。而 WebKit 在 getComputedStyle()执行中有个小bug,例如:返回值0.009999999776482582s会取代0.01s。
过渡延迟时间
transition-delay 属性规定了在执行一个过渡之前的等待时间,同样使用值。Delay 可以是负值,但这会导致动画无法平滑过渡。
IE 和 Opera 不接受 transition-duration 在-10ms和10ms之间的值。WebKit 的 floating point 也会在这儿出现。
http://www.cnblogs.com/surfaces/
另外属性相似点 animation-name:keyfrmaes1,keyfrmaes2;以逗号分割;
其他的
zepto核心动画但是实现了大部分常用的动画;在阅读器zepto核心动画源码的 时候,自己手动封装 实践;就机会发现 看似读懂其实不懂;自己正真封装调试后;才发现哪些巧;
封装后的 transform.js 源码如下
参数说明
/* * js transfrom * @param obj {obj} 原生dom对象 * @param properties {json} || string { translate3d:'220px,10px,0',left:'1em',opacity:0.2,perspective:'400px', rotateY:'30deg'} ||animation-name * @param duration {number} css3持续时间 秒 默认400毫秒 * @param ease {str} transition-timing-function 默认linear 支持 cubic-bezier(0.42,0,1,1)写法; * @param callback {function} 回调函数 * @param delay {number} 延迟时间 */
使用方法
/* http://www.cnblogs.com/surfaces/ * @param properties 为 {} 或者 string ;如果 properties= string 为animation-name * transform(elem, properties) * transform(elem, properties, ease) * transform(elem, properties, ease, delay) * transform(elem, properties, ease, callback, delay) * transform(elem, properties, callback) * transform(elem, properties, callback, delay) * transform(elem, properties, duration ) * transform(elem, properties, duration, ease) * transform(elem, properties, duration, delay) * transform(elem, properties, duration, callback) * transform(elem, properties, duration, callback,delay) * transform(elem, properties, duration, ease, delay) * transform(elem, properties, duration, ease, callback) * transform(elem, properties, duration, ease, callback,delay) transform(elem,{translateX:'150px',left:'1em',opacity:0.2,perspective:'400px', rotateY:'40deg'},600,'linear',function(){ console.log('结束回调') },200) ; transform(elem, keyframesName,600,'linear',function(){ console.log('结束回调') },200) ; */
移动端 transform.js 源码如下;双击直接复制即可;
;(function(window,document,undefined){var prefix=function(){var div=document.createElement("div");var cssText="-webkit-transition:all .1s;-moz-transition:all .1s; -Khtml-transition:all .1s; -o-transition:all .1s; -ms-transition:all .1s; transition:all .1s;";div.style.cssText=cssText;var style=div.style;var dom="";if(style.webkitTransition){dom="webkit"}else{if(style.MozTransition){dom="moz"}else{if(style.khtmlTransition){dom="Khtml"}else{if(style.oTransition){dom="o"}else{if(style.msTransition){dom="ms"}}}}}div=null;if(dom){return{dom:dom,lowercase:dom,css:"-"+dom+"-",js:dom[0].toUpperCase()+dom.substr(1)}}else{return false}}();var transitionEnd=function(){var el=document.createElement("div");var transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",msTransition:"MSTransitionEnd",transition:"transitionend"};for(var name in transEndEventNames){if(el.style[name]!==undefined){return transEndEventNames[name]}}el=null;return false}();var animationEnd=(function(){var eleStyle=document.createElement("div").style;var verdors=["a","webkitA","MozA","OA","msA"];var endEvents=["animationend","webkitAnimationEnd","animationend","oAnimationEnd","MSAnimationEnd"];var animation;for(var i=0,len=verdors.length;i<len;i++){animation=verdors[i]+"nimation";if(animation in eleStyle){return endEvents[i]}}return"animationend"}());var supportedTransforms=/^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i;var dasherize=function(str){return str.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()};function transform(obj,properties,duration,ease,callback,delay){if(!obj){return}if(typeof duration=="undefined"){duration=400;ease="linear";callback=undefined;delay=undefined}if(typeof duration=="string"){if(typeof ease=="number"){delay=ease;callback=undefined}if(typeof ease=="function"){delay=callback;callback=ease}ease=duration;duration=400}else{if(typeof duration=="function"){if(typeof ease=="number"){delay=ease}callback=duration;duration=400;ease="linear"}else{if(typeof duration=="number"){if(typeof ease=="undefined"){ease="linear"}else{if(typeof ease=="string"){ease=ease}else{if(typeof ease=="function"){if(typeof callback=="number"){delay=callback}callback=ease;ease="linear"}else{if(typeof ease=="number"){delay=ease;ease="linear"}}}}if(typeof callback=="number"){delay=callback;callback=undefined}}}}delay=(typeof delay=="number")?delay:0;var endEvent=transitionEnd;var nowTransition=prefix.js+"Transition";var nowTransform=prefix.js+"Transform";var prefixcss=prefix.css;if(!prefix.js){nowTransition="transition";nowTransform="transform";prefixcss=""}var transitionProperty,transitionDuration,transitionTiming,transitionDelay;var animationName,animationDuration,animationTiming,animationDelay;var key,cssValues={},cssProperties,transforms="";var transform;var cssReset={};var css="";var cssProperties=[];transform=prefixcss+"transform";cssReset[transitionProperty=prefixcss+"transition-property"]=cssReset[transitionDuration=prefixcss+"transition-duration"]=cssReset[transitionTiming=prefixcss+"transition-timing-function"]=cssReset[transitionDelay=prefixcss+"transition-delay"]=cssReset[animationName=prefixcss+"animation-name"]=cssReset[animationDuration=prefixcss+"animation-duration"]=cssReset[animationTiming=prefixcss+"animation-timing-function"]=cssReset[animationDelay=prefixcss+"animation-delay"]="";if(typeof properties=="string"){cssValues[animationName]=properties;cssValues[animationDuration]=duration+"ms";cssValues[animationTiming]=(ease||"linear");cssValues[animationDelay]=(delay)+"ms";endEvent=animationEnd}else{endEvent=transitionEnd;for(key in properties){if(supportedTransforms.test(key)){transforms+=key+"("+properties[key]+") "}else{cssValues[key]=properties[key],cssProperties.push(dasherize(key))}}if(transforms){cssValues[transform]=transforms,cssProperties.push(transform)}if(duration>0&&typeof properties==="object"){cssValues[transitionProperty]=cssProperties.join(", ");cssValues[transitionDuration]=duration+"ms";cssValues[transitionTiming]=(ease||"linear");cssValues[transitionDelay]=(delay)+"ms"}}for(var attr in cssValues){css+=dasherize(attr)+":"+cssValues[attr]+";"}obj.style.cssText=obj.style.cssText+";"+css;obj.clientLeft;if(!callback){return}var fired=false;var handler=function(event){if(typeof event!=="undefined"){if(event.target!==event.currentTarget){fired=true;return}}callback&&callback.apply(obj,arguments);fired=true;obj.removeEventListener(endEvent,arguments.callee,false)};if(obj.addEventListener){obj.addEventListener(endEvent,handler,false)}if(!endEvent||duration<=0){setTimeout(function(){handler()});return}setTimeout(function(){if(fired){return}handler()},parseInt((duration+delay)+25))}window.transform=transform})(window,document);
部分示例 使用代码
<style> *{ margin:0; padding:0;} ul,li{ list-style:none; margin:0; padding:0;} html{-webkit-touch-callout:none;-webkit-user-select:none;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent; font-size:62.5%; } body{ font-size:14px;} #leftsbox{ width:88%; height:auto; overflow:hidden; border:2px solid red; margin:0 auto; padding:30px 0;} #leftsbox div{ width:80px; height:80px; background:#6C6; margin-bottom:10px; position:relative; left:0; backface-visibility:hidden; -webkit-animation-fill-mode:forwards; animation-fill-mode:forwards; } @keyframes keyframesName{ 0%{ transform:translateX(0)} 100%{transform:translateX(600px) } } @-webkit-keyframes keyframesName{ 0%{ -webkit-transform:translateX(0)} 100%{-webkit-transform:translateX(600px) } } </style> <div id="leftsbox"> <div class="leftssa" style="background:red">1 lefts 22 RAF</div> <div class="leftssa " style="width:120px; background:#FF0">2 lefts 22 RAF</div> <div class="leftssa"> 333lefts 22 RAF</div> </div> <script> var leftsbox=document.getElementById("leftsbox"); var boxdiv=leftsbox.getElementsByTagName("div"); leftsbox.onclick=function(e){ for(var i=0;i<boxdiv.length;i++){ var that=boxdiv[i]; // transform(that,{ translate3d:'220px,10px,0',left:'1em',opacity:0.2,perspective:'400px', width:'200px', rotateY:'30deg'},800,'cubic-bezier(0.15, 0.5, 0.25, 1.0)',function(){ // this.innerHTML='结束回调'+this.innerHTML; // },1000*i); /* properties json(css3 属性) 或者 string(css3动画名称) * transform(elem, properties) * transform(elem, properties, ease) * transform(elem, properties, ease, delay) * transform(elem, properties, ease, callback, delay) * transform(elem, properties, callback) * transform(elem, properties, callback, delay) * transform(elem, properties, duration ) * transform(elem, properties, duration, ease) * transform(elem, properties, duration, delay) * transform(elem, properties, duration, callback) * transform(elem, properties, duration, callback,delay) * transform(elem, properties, duration, ease, delay) * transform(elem, properties, duration, ease, callback) * transform(elem, properties, duration, ease, callback,delay) */ //测试 properties为 keyframes var keyframes='keyframesName'; //transform(that, keyframes) //transform(that, keyframes,'ease') //transform(that, keyframes,'ease',1000*i) //transform(that, keyframes,'ease',function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) //transform(that, keyframes,function(){ this.innerHTML='结束回调'+this.innerHTML;}) //transform(that, keyframes,function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) //transform(that, keyframes,600) //transform(that, keyframes,600,ease') //transform(that, keyframes,600,1000*i) ; //transform(that, keyframes,600,function(){ this.innerHTML='结束回调'+this.innerHTML;}) ; //transform(that, keyframes,600,function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) ; //transform(that, keyframes,600,'ease',1000*i) // transform(that, keyframes,600,'ease',function(){ this.innerHTML='结束回调'+this.innerHTML;}) ; // transform(that, keyframes,600,function(){ this.innerHTML='结束回调'+this.innerHTML;},1020*i) ; // transform(that, keyframes,600,function(){ this.innerHTML='结束回调'+this.innerHTML;},-20*i) ; //css3 animation-delay 支持负数 直接进入到时间点 //测试 properties为 Json var json={translate3d:'600px,10px,0',left:'1em',opacity:0.2,perspective:'400px', width:'200px', rotateY:'30deg'} //transform(that,json); // transform(that, json,'ease') //transform(that, json,'ease',1000*i) //transform(that, json,'ease',function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) //transform(that, json,function(){ this.innerHTML='结束回调'+this.innerHTML;}) //transform(that, json,function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) //transform(that, json,600) //transform(that, json,600,'ease') //transform(that, json,600,1000*i) ; //transform(that, json,600,function(){ this.innerHTML='结束回调'+this.innerHTML;}) ; // transform(that, json,600,function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) ; //transform(that, json,600,'ease',1000*i) //transform(that, json,600,'ease',function(){ this.innerHTML='结束回调'+this.innerHTML;}) ; transform(that, json,600,'ease',function(){ this.innerHTML='结束回调'+this.innerHTML;},1000*i) ; //transform(that, json,600,'ease',function(){ this.innerHTML='结束回调'+this.innerHTML;},-100*i) ; ////css3 transition-delay 支持负数 直接进入到时间点 } //for end } </script>
唯一的缺点 scrollTop 缓动不支持; 这里有个简易的 函数 类似jquery 语法几乎一样 基于时间 算法 使用 requestAnimationFrame
先看效果
相关布局
<ul id="inner" > <li><a class="on" >护肤</a></li> <li><a >彩妆</a></li> <li><a >洗护</a></li> <li><a >套装</a></li> </ul> <ul class="uls" id="uls"> <li id='li1'></li> <li id='li12'></li> <li id='li3'></li> </ul> <div class="box22"> <div class="boxs">1111111111111111111111111111111111</div> <div class="boxs">22222222222222222222222222222</div> <div class="boxs">33333333333333333333333</div> <div class="boxs" style="height:100px;">4444444444444444444最后一个 </div> </div> <div id="gpTop">tops</div>
上图相关js
document.getElementById("gpTop").onclick=function(e){ var that=this; startMove(that,{"scrollTop":0},function(){ that.innerHTML='到顶了'; }); //width: 206px; height: 101px; opacity: 0.3; } var inner=document.getElementById("inner"); var inlione=inner.getElementsByTagName("li"); var box=document.querySelectorAll(".boxs"); for(var i=0;i<inlione.length;i++) { inlione[i].index=i; inlione[i].onclick=function(e){ var that=this; // console.log(that.index); var box2=box[that.index]; var nowTop=getOffest(box2).top; // console.log(nowTop); animate(window,{"scrollTop":nowTop},function(){ //console.log('success'); }); } }
如果通过原生js 获取 jquey的offset().top 的值呢 如下 这里
var getOffest=function (obj){ var top=0,left=0; if(obj){ while(obj.offsetParent){ top += obj.offsetTop; left += obj.offsetLeft; obj = obj.offsetParent; } } return{ top : top, left : left } }
animate.js基于 requestAnimationFrame 兼容ie8+ (单位px 未做rem 兼容处理 ,没有jquery的 stop()处理)
语法如下
animate(dom元素,{"left":1300,"opacity":90},持续时间(毫秒),缓动形式(tween函数),回调函数);
animate(element,{"left":1300,"opacity":90},2000,'easeOut',function sa(){ console.log('回调函数'); });
为什么要用 requestAnimationFrame 。
浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。比如,通过requestAnimationFrame()
,JS动画能够和CSS动画/变换或SVG SMIL动画同步发生。另外,如果在一个浏览器标签页里运行一个动画,当这个标签页不可见时,浏览器会暂停它,这会减少CPU,内存的压力,节省电池电量。
相信日后 requestAnimationFrame 在移动端发展的前景是相当不错的,目前安卓上感觉还是有点丢帧。抖动明显;
requestAnimationFrame 一个简单的例子
var start = null; var ele = document.getElementById("j_precent"); var progress = 0; function step() { progress += 1; ele.style.width = progress + "%"; ele.innerHTML=progress + "%"; if (progress < 100) { requestAnimationFrame(step); } } document.getElementById("btn_run").addEventListener("click", function() { ele.style.width = "1px"; progress = 0; step() }, false);
如下 jQuery1.6.2还用 requestAnimationFrame;
// Start an animation from one number to another custom: function( from, to, unit ) { var self = this, fx = jQuery.fx, raf; this.startTime = fxNow || createFxNow(); this.start = from; this.end = to; this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); this.now = this.start; this.pos = this.state = 0; function t( gotoEnd ) { return self.step(gotoEnd); } t.elem = this.elem; if ( t() && jQuery.timers.push(t) && !timerId ) { // Use requestAnimationFrame instead of setInterval if available if ( requestAnimationFrame ) { timerId = true; raf = function() { // When timerId gets set to null at any point, this stops if ( timerId ) { requestAnimationFrame( raf ); fx.tick(); } }; requestAnimationFrame( raf ); } else { timerId = setInterval( fx.tick, fx.interval ); } } },
http://stackoverflow.com/questions/7999680/why-doesnt-jquery-use-requestanimationframe
为啥jquery 放弃,上面说的比较完善;
animate源码如下
;(function() { var lastTime = 0; var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function(id) { clearTimeout(id); }; } }()); var getStyle=function (obj,attr){ return obj.currentStyle ? obj.currentStyle[attr]:getComputedStyle(obj)[attr]; } 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; } } function animate(obj,json,times,fx,fn){ if( typeof times == 'undefined' ){ times = 400; fx = 'linear'; } if( typeof times == 'string' ){ if(typeof fx == 'function'){ fn = fx; } fx = times; times = 400; } else if(typeof times == 'function'){ fn = times; times = 400; fx = 'linear'; } else if(typeof times == 'number'){ if(typeof fx == 'function'){ fn = fx; fx = 'linear'; } else if(typeof fx == 'undefined'){ fx = 'linear'; } } var iCur = {}; var startTime = +new Date(); for(var attr in json){ iCur[attr] = 0; if( attr == 'opacity' ){ if(Math.round(getStyle(obj,attr)*100) == 0){ iCur[attr] = 0; } else{ iCur[attr] = Math.round(getStyle(obj,attr)*100) || 100; } } else if(attr == 'scrollTop' ){ iCur[attr]=window.scrollY||document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;; } else{ iCur[attr] = parseInt(getStyle(obj,attr)) || 0; } } if(obj.timer){ cancelAnimationFrame(obj.timer) } //obj.timer=null; function update(){ var changeTime = +new Date(); var scale = 1 - Math.max(0,startTime - changeTime + times)/times; for(var attr in json){ var value = Tween[fx](scale*times, iCur[attr] , json[attr] - iCur[attr] , times ); if(attr == 'opacity'){ obj.style.filter = 'alpha(opacity='+ value +')'; obj.style.opacity = value/100; }else if(attr == 'scrollTop'){ window.scrollTo(0, value); }else{ obj.style[attr] = value + 'px'; } } if(scale == 1){ cancelAnimationFrame(obj.timer); fn&&fn.apply(this, arguments); }else{ //setup_fps_meters(); obj.timer=requestAnimationFrame(arguments.callee); } }//update end // requestAnimationFrame(update); //某些时候 画的过快 有bug update(); }
Tween.js /* * t : time 已过时间 * b : begin 起始值 * c : count 总的运动值 * d : duration 持续时间 * */ //Tween.linear();
css cubic-bezier 缓动备份查看 注意点:x1,x2,y1,y2的值范围在[0, 1]。
var easingMap = { "linear": [0.250, 0.250, 0.750, 0.750], "ease": [0.250, 0.100, 0.250, 1.000], "easeIn": [0.420, 0.000, 1.000, 1.000], "easeOut": [0.000, 0.000, 0.580, 1.000], "easeInOut": [0.420, 0.000, 0.580, 1.000], "easeInQuad": [0.550, 0.085, 0.680, 0.530], "easeInCubic": [0.550, 0.055, 0.675, 0.190], "easeInQuart": [0.895, 0.030, 0.685, 0.220], "easeInQuint": [0.755, 0.050, 0.855, 0.060], "easeInSine": [0.470, 0.000, 0.745, 0.715], "easeInExpo": [0.950, 0.050, 0.795, 0.035], "easeInCirc": [0.600, 0.040, 0.980, 0.335], "easeInBack": [0.600, -0.280, 0.735, 0.045], "easeOutQuad": [0.250, 0.460, 0.450, 0.940], "easeOutCubic": [0.215, 0.610, 0.355, 1.000], "easeOutQuart": [0.165, 0.840, 0.440, 1.000], "easeOutQuint": [0.230, 1.000, 0.320, 1.000], "easeOutSine": [0.390, 0.575, 0.565, 1.000], "easeOutExpo": [0.190, 1.000, 0.220, 1.000], "easeOutCirc": [0.075, 0.820, 0.165, 1.000], "easeOutBack": [0.175, 0.885, 0.320, 1.275], "easeInOutQuad": [0.455, 0.030, 0.515, 0.955], "easeInOutCubic": [0.645, 0.045, 0.355, 1.000], "easeInOutQuart": [0.770, 0.000, 0.175, 1.000], "easeInOutQuint": [0.860, 0.000, 0.070, 1.000], "easeInOutSine": [0.445, 0.050, 0.550, 0.950], "easeInOutExpo": [1.000, 0.000, 0.000, 1.000], "easeInOutCirc": [0.785, 0.135, 0.150, 0.860], "easeInOutBack": [0.680, -0.550, 0.265, 1.550], "custom": [0.000, 0.350, 0.500, 1.300], "random": [Math.random().toFixed(3), Math.random().toFixed(3), Math.random().toFixed(3), Math.random().toFixed(3)] }
This curve contains values out of range. But fear not young padawan! Just use cubic-bezier(.25,.1,.39,1)
as well for Webkit until the bug #45761 fix propagates to Safari.
接近ios 原生 的切换 cubic-bezier(0.42, 0, 0.58, 1.0)
http://www.cnblogs.com/surfaces/
// transform兼容 function preTransform() { var cssPrefix, vendors = { '': '', Webkit: 'webkit', Moz: '', O: 'o', ms: 'ms' }, testEle = document.createElement('p'), cssSupport = {}; // 嗅探特性 Object.keys(vendors).some(function(vendor) { if (testEle.style[vendor + (vendor ? 'T' : 't') + 'ransform'] !== undefined) { cssPrefix = vendor ? '-' + vendor.toLowerCase() + '-' : ''; return true; } }); function normalizeCss(name) { name = name.toLowerCase(); return cssPrefix ? cssPrefix + name : name; } cssSupport = { transform: normalizeCss('Transform'), } return cssSupport.transform; } }());
使用tween 单独的 , easeIn1 自定义的,
var easeIn1= Tween.easeIn=function(t, b, c, d){ //加速曲线
return c*(t/=d)*t + b;
}
https://stackoverflow.com/questions/8917921/cross-browser-javascript-not-jquery-scroll-to-top-animation/16136789#16136789
document.getElementById("asdad").onclick = function() { function easeInOutQuad(t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return -c / 2 * (t * (t - 2) - 1) + b; }; function easeIn1(currentTime, start, change, duration) { currentTime /= duration / 2; if (currentTime < 1) { return change / 2 * currentTime * currentTime + start; } currentTime -= 1; return -change / 2 * (currentTime * (currentTime - 2) - 1) + start; } console.log("body goTop", document.scrollingElement.scrollTop) // scrollTo(document.scrollingElement, 0, 1250,easeIn1); scrollTo(document.scrollingElement, 100, 1250, easeIn1); } function scrollTo(element, to, duration, easefn) { var start = element.scrollTop, change = to - start, increment = 20; var easefn = easefn //console.log(start,document.body.scrollTop,easefn===easeInOut,"easefn",typeof easefn) var animateScroll = function(elapsedTime) { elapsedTime += increment; var position = easefn(elapsedTime, start, change, duration); element.scrollTop = position; if (elapsedTime < duration) { setTimeout(function() { animateScroll(elapsedTime); }, increment); } }; animateScroll(0); }
示例
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="initial-scale=1.0,user-scalable=no,maximum-scale=1,width=device-width" /> <title>scrollAnimate</title> </head> <style> * { margin: 0; padding: 0; } ul, li { margin: 0; padding: 0; list-style: none; } .scroll-box { background-color: #EEEEEE; border: 1px solid red; height: 500px; overflow-y: hidden; touch-action: pan-y; ; } .scroll-box p { min-height: 50px; ; } .scroll-box p:nth-child(odd) { background-color: #999; } </style> <body> <p style="margin: 20px auto; text-align: center;">------ -------start- <button id="j_dom">点击观察</button>-----------------</p> <div class="scroll-box " id="j_target" style="width: 80%; height: 400px;; margin: 0 auto;"> <p style="height: 100px;background-color: red;;">111111111111111</p> <p>22222</p> <p style="height: 600px;background-color: #ddd;;">33</p> <p>44</p> <p style="height: 100px; background-color: red;">55</p> </div> <p style="margin: 20px auto; text-align: center;">------ -------end------------------</p> <script> window.onload = function() { function getArrlist(len, height) { var strHtml = "" var len = 500; var height = 100; for (var i = 0; i < len; i++) { var str = ''; if (i == 0) { str = "开始" } else if (i == len - 1) { str = "最后" } else { str = i } strHtml += '<p index="' + i + '">' + str + '</p>' } return strHtml; } function moveTo(element, start, to, prop, duration, easing, endFn, stepFn) { var prop = prop; var start = start || 0; var change = to - start; var duration = duration; var easeInOut = easing; var increment = 13; console.log("moveTo", start, "easefn", easeInOut) var animateScroll = function(elapsedTime) { elapsedTime += increment; var value = easeInOut(elapsedTime, start, change, duration); element.style[prop] = value + 'px'; stepFn && stepFn.call(this, value); if (elapsedTime < duration) { setTimeout(function() { animateScroll(elapsedTime); }, increment); } else { element.style[prop] = to + 'px'; endFn && endFn.call(this, value); } }; animateScroll(0); } function scrollAnimate(element, start, to, prop, duration, easing, endFn, stepFn) { var prop = prop; var start = start || 0; var change = parseInt(to - start) var duration = duration; var easeInOut = easing; var startTime = (+new Date); //var currentY= parseInt(element.scrollTop); console.log("scrollAnimate", start, "easefn", easeInOut) step(); //结束时间的计算 function step() { var changeTime = +new Date(); var scale = 1 - Math.max(0, startTime - changeTime + duration) / duration; var value = easeInOut(scale * duration, start, change, duration); // var easingFunc = BezierEasing(.42, 0, 1, 1); // var percent = easingFunc(scale); // var value = start + to * percent; //console.log("scrollAnimate", scale, "percent", percent) // var value = Tween[easing](scale*duration,targetY,change,duration); //var value = easeInOut(scale * duration, start, change, duration); //console.log("scale",scale,"已过时间",(changeTime-startTime),"总时间",duration,"已过时间百分比",(changeTime-startTime)/duration,'change距离',change) // console.log(value,"scale",scale,"已过时间百分比",(changeTime-startTime)/duration,'change距离',change) //得到新的坐标值后我们继续通过left/top或者transform来完成计算就可以了 element.style[prop] = value + 'px'; stepFn && stepFn.call(this, value); if (scale == 1) { // console.log("运动结束"); cancelAnimationFrame(element.timer); endFn && endFn.call(this, value); return; } else { element.timer = requestAnimationFrame(step); } } //https://github.com/alvarotrigo/fullPage.js/blob/master/src/fullpage.js } function easeInOutQuad(t, b, c, d) { t /= d / 2; if (t < 1) return c / 2 * t * t + b; t--; return -c / 2 * (t * (t - 2) - 1) + b; }; var j_target = document.getElementById("j_target"); var j_dom = document.getElementById("j_dom") j_target.innerHTML = getArrlist(); j_target.scrollTop = 100; j_dom.addEventListener("click", function() { console.log("click ", j_target); //scrollAnimate(element, start, to, prop, duration, easing, endFn, stepFn) scrollAnimate(j_target, 0, 1200, "scrollTop", 1400, easeInOutQuad, function() { //console.log("外面运动中"); }, function(val) { j_target.scrollTop = val; console.log("外面运动中"); }) return false; }) } </script> <script> </script> </body>
腾讯的一个例子 http://alloyteam.github.io/AlloyFinger/example/picture/
var To=function (el, property, value, time, ease, onEnd,onChange ) { var current = el[property]; var dv = value - current; var beginTime = new Date(); var self = this; var currentEase=ease||function(a){return a }; this.tickID=null; var toTick = function () { var dt = new Date() - beginTime; if (dt >= time) { el[property] = value; onChange && onChange(value); onEnd && onEnd(value); cancelAnimationFrame(self.tickID); self.toTick=null; return; } el[property] = dv * currentEase(dt / time) + current; self.tickID=requestAnimationFrame(toTick); //self.tickID = requestAnimationFrame(toTick); //cancelAnimationFrame������ tickID = requestAnimationFrame(toTick);��� onChange && onChange(el[property]); }; toTick(); To.List.push(this); }; To.List=[]; To.stopAll=function(){ for(var i= 0,len=To.List.length;i<len;i++){ cancelAnimationFrame(To.List[i].tickID); } To.List.length=0; }; To.stop=function(to) { cancelAnimationFrame(to.tickID); };
调用方式 new To(el, "translateX", x, 500, ease);
<!DOCTYPE html> <html> <head> <title>AlloyFinger</title> <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/> <style> .ribbon { top: 3.2em; right: -4.7em; -webkit-transform: rotate(45deg); -moz-transform: rotate(45deg); -ms-transform: rotate(45deg); -o-transform: rotate(45deg); transform: rotate(45deg); color:#fff; display: block; padding: .6em 3.5em; position: fixed; text-align: center; text-decoration: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: green; z-index: 10000; } </style> </head> <body> <a href="https://github.com/AlloyTeam/AlloyFinger" class="ribbon">Fork me on Github</a> <script src="../../asset/transform.js"></script> <script src="../../alloy_finger.js"></script> <script src="../../asset/image_loaded.js"></script> <script src="../../asset/to.js"></script> <div id="imgBox" style="position:fixed;width: 100%;height: 100%;left:0;top:0; background:black;display: none;"> <img src="../../asset/cover.jpg" id="testImg" alt="" style="width: 100%;position: absolute; " /> </div> <script> var topPx; imageLoaded("#testImg",function(w,h){ document.querySelector("#imgBox").style.display="block"; topPx=window.innerHeight/2-(h*window.innerWidth/w)/2; this.style.top=topPx+"px"; }); function ease(x) { return Math.sqrt(1 - Math.pow(x - 1, 2)); } var el = document.getElementById("testImg"); Transform(el); var initScale = 1; new AlloyFinger(el, { multipointStart: function () { To.stopAll(); initScale = el.scaleX; }, rotate: function (evt) { el.rotateZ += evt.angle; }, pinch: function (evt) { el.scaleX = el.scaleY = initScale * evt.zoom; }, multipointEnd: function () { To.stopAll(); if (el.scaleX < 1) { new To(el, "scaleX", 1, 500, ease); new To(el, "scaleY", 1, 500, ease); } if (el.scaleX > 2) { new To(el, "scaleX", 2, 500, ease); new To(el, "scaleY", 2, 500, ease); } var rotation = el.rotateZ % 360; if (rotation < 0)rotation = 360 + rotation; el.rotateZ=rotation; if (rotation > 0 && rotation < 45) { new To(el, "rotateZ", 0, 500, ease); } else if (rotation >= 315) { new To(el, "rotateZ", 360, 500, ease); } else if (rotation >= 45 && rotation < 135) { new To(el, "rotateZ", 90, 500, ease); } else if (rotation >= 135 && rotation < 225) { new To(el, "rotateZ", 180, 500, ease); } else if (rotation >= 225 && rotation < 315) { new To(el, "rotateZ", 270, 500, ease); } }, pressMove: function (evt) { el.translateX += evt.deltaX; el.translateY += evt.deltaY; evt.preventDefault(); }, tap: function (evt) { //console.log(el.scaleX + "_" + el.scaleY + "_" + el.rotateZ + "_" + el.translateX + "_" + el.translateY); //console.log("tap"); }, doubleTap: function (evt) { To.stopAll(); if (el.scaleX > 1.5) { new To(el, "scaleX", 1, 500, ease); new To(el, "scaleY", 1, 500, ease); new To(el, "translateX", 0, 500, ease); new To(el, "translateY", 0, 500, ease); } else { var box = el.getBoundingClientRect(); var y = box.height - (( evt.changedTouches[0].pageY - topPx) * 2) - (box.height / 2 - ( evt.changedTouches[0].pageY - topPx)); var x = box.width - (( evt.changedTouches[0].pageX) * 2) - (box.width / 2 - ( evt.changedTouches[0].pageX)); new To(el, "scaleX", 2, 500, ease); new To(el, "scaleY", 2, 500, ease); new To(el, "translateX", x, 500, ease); new To(el, "translateY", y, 500, ease); } //console.log("doubleTap"); }, longTap: function (evt) { //console.log("longTap"); }, swipe: function (evt) { //console.log("swipe" + evt.direction); } }); </script> </body> </html>