jQueryのシミュレーションは$().animate()(下)
9359 ワード
前言:前編の基礎の上で、
論理図:
実装:
実行結果:
解析:(1)単一のアニメーション自体にもループがあり、つまり
(完)
doAnimation()
にアクセスします論理図:
実装:
jQuery $().animate()
A
// (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'
});
};
実行結果:
解析:(1)単一のアニメーション自体にもループがあり、つまり
requestAnimationFrame
を利用してアニメーションフレームをループし、それによってアニメーションを描画する(2)percent
<1の場合、すなわちアニメーションの実行時間が全体の時間より小さく、アニメーションフレームを絶えず実行する.percent
=1の場合、アニメーションが終了したことを示し、アニメーションキューに通知し、次のアニメーションを実行し、ループすればよい(完)