前言:
在上篇的基礎上,接入doAnimation()
javascript
邏輯圖:html
實現:java
<!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>
複製代碼
運行結果:web
解析:
(1)單個動畫自己也有循環,也就是利用requestAnimationFrame
循環動畫幀,從而繪製動畫
(2)當percent
<1 時,即動畫運行時間小於整體時間,就不斷運行動畫幀;當percent
=1 時,表示動畫結束,通知動畫隊列,運行下個動畫,如此循環便可算法
(完)緩存