前言:
在上篇的基礎上,接入doAnimation()
html
邏輯圖:算法
實現:緩存
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>jQuery之$().animate()的實現</title> </head> <body> <!--<script src="jQuery.js"></script>--> <div id="A" style="width:100px;height:50px;background-color: deeppink">這是A</div> <script> // (function(a){ // console.log(a) //name // })('name') // // (function (b) { // console.log(b) //function(){console.log('name')} // })(function () { // console.log('name') // }) //匿名函數自調用,下面好長好長的function就是$ //也就是說$是一個function(){xxx} (function($) { window.$ = $; })( //這裏也是匿名函數自調用 //本質就是通過一系列操做獲得chenQuery並做爲參數$,賦值給window.$ function() { //匹配ID let rquickExpr = /^(?:#([\w-]*))$/; //jQuery初始化 function chenQuery(selector) { return new chenQuery.fn.init(selector); } function getStyles(elem) { return elem.ownerDocument.defaultView.getComputedStyle(elem, null); } //模仿swing動畫效果 //兩頭慢,中間快 function swing(p) { return 0.5 - Math.cos(p * Math.PI) / 2; } //建立動畫緩動對象// function Tween(value, prop, animation) { this.elem=animation.elem; this.prop=prop; this.easing= "swing"; //動畫緩動算法 this.options=animation.options; //獲取初始值 this.start=this.now = this.get(); //動畫最終值 this.end= value; //單位 this.unit="px" } Tween.prototype = { //獲取元素的當前屬性 get: function() { let computed = getStyles(this.elem); let ret = computed.getPropertyValue(this.prop) || computed[this.prop]; return parseFloat(ret); }, //運行動畫 run:function(percent){ let eased //根據緩動算法改變percent this.pos = eased = swing(percent); //獲取具體的改變座標值 this.now = (this.end - this.start) * eased + this.start; //最終改變座標 this.elem.style[this.prop] = this.now + "px"; return this; } } //建立開始時間 function createFxNow() { setTimeout(function() { Animation.fxNow = undefined; }); return (Animation.fxNow = Date.now()); } let inProgress function schedule() { //inProgress是判斷整個動畫流程是否結束的標誌 //當inProgress=null時,整個動畫結束 if ( inProgress ) { //走這邊 //使用requestAnimationFrame來完成動畫 //遞歸 window.requestAnimationFrame( schedule ); /*執行動畫幀*/ Animation.fx.tick(); } } // 動畫核心函數 function Animation(elem, options, optall,func,){ //動畫對象 let animation = { elem:elem, props:options, originalOptions:optall, options:optall, //動畫開始時間 startTime:Animation.fxNow || createFxNow(), //存放每一個屬性的緩動對象,用於動畫 tweens:[] } //生成屬性對應的動畫算法對象 for (let k in options) { // tweens保存每個屬性對應的緩動控制對象 animation.tweens.push( new Tween(options[k], k, animation) ) } //動畫狀態 let stopped; //動畫的定時器調用包裝器 //單幀循環執行 let tick = function() { if (stopped) { return false; } //動畫時間算法 let currentTime = Animation.fxNow || createFxNow, //動畫運動時間遞減 remaining = Math.max(0, animation.startTime + animation.options.duration - currentTime), //百分比 temp = remaining / animation.options.duration || 0, percent = 1 - temp; let index = 0, length = animation.tweens.length; //執行動畫改變 for (; index < length; index++) { //percent改變值 animation.tweens[index].run(percent); } //當進度不到100%時,繼續繪製動畫幀 if (percent < 1 && length) { return remaining; } //當結束時通知單個動畫結束 tick.complete() return false } tick.elem = elem; tick.anim = animation //這個是自定義的屬性,也就是單個動畫結束的標誌 tick.complete = func //開始執行下個動畫 Animation.fx.timer(tick) } //用於requestAnimationFrame調用 Animation.timers =[] Animation.fx = { //開始動畫隊列,不是幀隊列 //Animation.tick() timer: function(timer,) { Animation.timers.push(timer); if (timer()) { //開始執行動畫 Animation.fx.start(); // func() } // else { // Animation.timers.pop(); // } }, //開始循環 start: function(func) { if ( inProgress ) { return; } //動畫開始即爲運行中,加上鎖 inProgress = true; //運行 schedule(); // func() }, //中止循環 stop:function(){ inProgress = null; }, //動畫幀循環的的檢測 tick: function() { var timer, i = 0, timers = Animation.timers; Animation.fxNow = Date.now(); for (; i < timers.length; i++) { timer = timers[i]; if (!timer() && timers[i] === timer) { //若是完成了就刪除這個動畫 timers.splice(i--, 1); } } if (!timers.length) { Animation.fx.stop(); } Animation.fxNow = undefined; } } //假設是在數據緩存中存取隊列 const Queue=[] //數據緩存 const dataPriv={ get:function (type) { if(type==='queue') return Queue }, } const dequeue=function() { const Queue=dataPriv.get("queue") let fn = Queue.shift() //當單個動畫結束後,執行下個動畫 const next = function() { dequeue(); } if ( fn === "inprogress" ) { fn = Queue.shift(); } if (fn) { Queue.unshift( "inprogress" ); /*執行doAnimation方法,doAnimation(element, options,function() {firing = false;_fire();})*/ /*fn的參數就是形參func*/ /*func方法是用來通知上個動畫結束,下個動畫運行的重要function*/ //func的做用是用來通知動畫執行結束,並繼續執行下一個動畫 const func=function() { next(); } fn(func); } } //省略type const queue=function(element, options, callback, ) { //模仿從數據緩存中獲得的隊列,直接寫Queue.push也行 const Queue=dataPriv.get("queue") //向動畫隊列中添加doAnimation觸發器 Queue.push(function(func) { //doAnimation callback(element, options, func); }); //若是沒有動畫在運行,運行動畫 //動畫鎖inprogress if(Queue[0]!=='inprogress'){ dequeue() } } /*動畫*/ const animation = function(element,options) { const doAnimation = function(element, options, func) { // const width = options.width /*===這裏面定義了動畫的算法,也就是Animation實現的地方===*/ // 默認動畫時長2s // element.style.transitionDuration = '400ms'; // element.style.width = width + 'px'; /*監聽單個動畫完結*/ //transitionend 事件在 CSS 完成過渡後觸發 // element.addEventListener('transitionend', function() { // func() // }); //動畫的默認屬性 let optall={ complete:function(){}, old:false, duration: 400, easing: undefined, queue:"fx", } let anim=Animation(element, options, optall,func) } //每調用一次animation,就入一次隊 return queue(element, options,doAnimation,); } //爲chenQuery的fn和prototype原型屬性 賦 animate屬性 chenQuery.fn = chenQuery.prototype = { //也能夠直接把animation裏的代碼放這裏來,但這樣就太長了,下降了可讀性 animate: function(options) { animation(this.element, options); //注意返回的是this,也就是$("#A"),這樣就能繼續調用animate方法 // 也就是鏈式調用 return this; } } //爲chenQuery的fn屬性添加init方法 const init = chenQuery.fn.init = function(selector) { // ["#A", "A",groups: undefined,index: 0,input: "#A"] const match = rquickExpr.exec(selector); //這邊默認是隻找id的元素 const element = document.getElementById(match[1]) //this指chenQuery.fn.init方法 //爲該方法添加element屬性 this.element = element; //返回chenQuery.fn.init return this; } //挺繞的,再將init的原型等於chenQuery.fn方法 init.prototype = chenQuery.fn; //chenQuery自己是一個function(){} // chenQuery{ //init能調用fn,fn能調用init // fn:{ // animate:function(){}, // init:function(){}, // // init.prototype=fn // }, // prototype:{ // animate:function(){}, // } // } return chenQuery; }()); const A = document.querySelector('#A'); //在異步調用中,進行同步調用 //動畫是異步的 A.onclick = function() { //就是連續調用animation.add() $('#A').animate({ 'width': '500' }).animate({ 'width': '300' }).animate({ 'width': '1000' }); }; </script> </body> </html>
運行結果:異步
解析:
(1)單個動畫自己也有循環,也就是利用requestAnimationFrame
循環動畫幀,從而繪製動畫
(2)當percent
<1 時,即動畫運行時間小於整體時間,就不斷運行動畫幀;當percent
=1 時,表示動畫結束,通知動畫隊列,運行下個動畫,如此循環便可函數
(完)動畫