前言:
在上篇的基础上,接入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 时,表示动画结束,通知动画队列,运行下个动画,如此循环便可函数
(完)动画