移动端 transition动画函数的封装(仿Zepto)以及 requestAnimationFrame动画函数封装(仿jQuery)
移动端 css3 transition 动画 ,requestAnimationFrame 动画 对于性能的要求,h5优先考虑;
移动端 单页有时候 制作只用到简单的css3动画即可,我们封装一下,没必要引入zepto框架,把zepto的动画模块从Zepto 扒下来,加以改造独立;用于生产环境;下面是 Demo栗子;
上面图片对应的 js
1 2 3 4 5 6 7 8 9 | 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 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | < 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 动画书写方式
1 2 3 4 5 6 7 8 9 10 11 12 13 | $( "#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(单位:毫秒));
1 2 3 4 5 6 7 8 9 10 | /* * 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} 延迟时间 */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* 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). 过渡单个属性:
1 2 3 4 | transition-property:opacity; transition-duration:2s; transition-timing-function:ease-in; transition-delay:0; |
2). 过渡多个属性:
[1]. 上下一一对应型:
1 2 3 4 | transition-property:opacity left; transition-duration:2s, 4s; transition-timing-function:ease-in; transition-delay:0; |
此时:opacity过渡时间为2s,left过渡时间为4s。
[2]. 循环对应型:
1 2 3 4 | 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 源码如下
参数说明
1 2 3 4 5 6 7 8 9 10 | /* * 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} 延迟时间 */ |
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* 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 源码如下;双击直接复制即可;
1 | ;( 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); |
部分示例 使用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | < 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函数),回调函数);
1 2 3 | animate(element,{ "left" :1300, "opacity" :90},2000, 'easeOut' , function sa(){ console.log( '回调函数' ); }); |
为什么要用 requestAnimationFrame 。
浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。比如,通过requestAnimationFrame()
,JS动画能够和CSS动画/变换或SVG SMIL动画同步发生。另外,如果在一个浏览器标签页里运行一个动画,当这个标签页不可见时,浏览器会暂停它,这会减少CPU,内存的压力,节省电池电量。
相信日后 requestAnimationFrame 在移动端发展的前景是相当不错的,目前安卓上感觉还是有点丢帧。抖动明显;
requestAnimationFrame 一个简单的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | // 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/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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); } |
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | <! 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 > |
BezierEasing 开源库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | /** * https://github.com/gre/bezier-easing * BezierEasing - use bezier curve for transition easing function * by Gaëtan Renaudeau 2014 - 2015 – MIT License */ // These values are established by empiricism with tests (tradeoff: performance VS precision) var NEWTON_ITERATIONS = 4; var NEWTON_MIN_SLOPE = 0.001; var SUBDIVISION_PRECISION = 0.0000001; var SUBDIVISION_MAX_ITERATIONS = 10; var kSplineTableSize = 11; var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); var float32ArraySupported = typeof Float32Array === 'function'; function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } function C (aA1) { return 3.0 * aA1; } // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } function binarySubdivide (aX, aA, aB, mX1, mX2) { var currentX, currentT, i = 0; do { currentT = aA + (aB - aA) / 2.0; currentX = calcBezier(currentT, mX1, mX2) - aX; if (currentX > 0.0) { aB = currentT; } else { aA = currentT; } } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS ); return currentT; } function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { for (var i = 0; i < NEWTON_ITERATIONS; ++i) { var currentSlope = getSlope(aGuessT, mX1, mX2); if (currentSlope === 0.0) { return aGuessT; } var currentX = calcBezier(aGuessT, mX1, mX2) - aX; aGuessT -= currentX / currentSlope; } return aGuessT; } function LinearEasing (x) { return x; } module.exports = function bezier (mX1, mY1, mX2, mY2) { if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { throw new Error('bezier x values must be in [0, 1] range'); } if (mX1 === mY1 && mX2 === mY2) { return LinearEasing; } // Precompute samples table var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); for (var i = 0; i < kSplineTableSize; ++i) { sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); } function getTForX (aX) { var intervalStart = 0.0; var currentSample = 1; var lastSample = kSplineTableSize - 1; for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { intervalStart += kSampleStepSize; } --currentSample; // Interpolate to provide an initial guess for t var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); var guessForT = intervalStart + dist * kSampleStepSize; var initialSlope = getSlope(guessForT, mX1, mX2); if (initialSlope >= NEWTON_MIN_SLOPE) { return newtonRaphsonIterate(aX, guessForT, mX1, mX2); } else if (initialSlope === 0.0) { return guessForT; } else { return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); } } return function BezierEasing (x) { // Because JavaScript number are imprecise, we should guarantee the extremes are right. if (x === 0 || x === 1) { return x; } return calcBezier(getTForX(x), mY1, mY2); }; }; |
var easing = BezierEasing(0, 0, 1, 0.5);
// easing allows to project x in [0.0,1.0] range onto the bezier-curve defined by the 4 points (see schema below).
console.log(easing(0.0)); // 0.0
console.log(easing(0.5)); // 0.3125
console.log(easing(1.0)); // 1.0
腾讯的一个例子 http://alloyteam.github.io/AlloyFinger/example/picture/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | <!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> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话