说好的缓动呢?
jQuery的缓动函数不给力,被逼自己写一个,既然要写,先把需求列好:
1. 动画类型有:linear, easeIn, easeOut, easeInOut
2. 可以在缓动过程中改变属性,也可以是调用函数,并改变函数的参数(后者是我的初衷,jq只支持属性,见 当0碰上0 )
动画是怎样产生的?学过Flash的人应该知道帧,帧就是一个画面,通常一秒25帧,也就一秒播放25个画面,如此快速的放过去,静态也成了动态(当然,前提是25个画面不是同一个画面,囧。。。)
难道做动画要画N多图,好吧,这叫“逐帧动画”,如果你很闲大可以这么干,因为这样做的效果绝对是顶级的,毫无瑕疵的。但是我一点都不闲,我画画的水平连幼儿园都教不了,所以这条路可以直接枪毙。
接下来进入正题,Flash中还有关键帧的概念,举个例子,关键帧A,物体box的宽高为100,关键帧B,box的宽高为50,A与B之间有25帧,接着我们从A播放到B,效果就是box在1秒内不断缩小,这叫“补间动画”。
这里涉及到三个东西:动画对象(box),动画属性(width, height),动画时间(1秒)
我们要做的事就是在某个时间点确定动画属性的值
常见的动画有四种类型,介绍一下:
linear:线性动画,即匀速
easeIn:速度从小到大,即淡入
easeOut :速度从大到小,即淡出
easeInOut:开始时速度从小到大,结束时速度从大到小,即淡入淡出
====================下面这段爱看不看,估计看着挺无聊的===========================
其实说到缓动,就不得不提Robert Penner,他发明了N多缓动公式,举个例子
1 2 3 4 5 6 7 8 9 10 | /** * @param t 用掉的时间 * @param b 初始值 * @param c 总变化量,比如x的初始值为0,结束值为100,变化量就是100 * @param d 动画总时长 * @return 当前属性值 */ function linear(t, b, c, d){ return c * t / d + b; } |
我还是解释一下吧:
设当前变化量为X,则
t / d = X / c,所以X = c * t / d,然后X + b就可以获得当前属性值
再看一个稍复杂的:
1 2 3 | var easeInQuad = function (t, b, c, d){ return c * ( t /= d ) * t + b; } |
这个有淡入效果,也就是说动画开始时,值的变化量从小到大。
可以发现两者唯一的区别就是 t / d 和 (t /= d) * t,刚才说了t / d是一个>=0 && <=1的比值,暂取名为a,而(t /= d) * t就相当于Math.pow(a, 2)。
为什么要平方呢?
1. 首先a >= Math.pow(a, 2)是肯定的
2. 每次调用函数时,t / d 这个比值也是匀速变大的,比如第1次调用时是0.1(平方0.01),第2次调用时是0.2(平方0.04)等,那第10次调用时,肯定是1没错吧,这时候 c * 1 + b,动画就到此结束了
3. 第2点证明了比值越小,值的变化量就越小,比值越大,值的变化量就越大,如果不用平方而是三次方,那淡入效果就更明显了。
举个两个例子,可以看出一些规律,公式大体便是 当前属性值 = 总变化量 * 增量系数 + 初始值, 其中,增量系数根据不同算法各有不同。
====================继续往下看吧===========================
增量系数的算法,我就从 司徒正美 那拿过来用吧,他总结了一大堆,必须收藏啊!!
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 189 190 191 192 | var tween = { linear : function (pos) { return 1; }, easeInQuad : function (pos) { return Math.pow(pos, 2); }, easeOutQuad : function (pos) { return -(Math.pow(( pos - 1), 2) - 1); }, easeInOutQuad : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 2); return -0.5 * ((pos -= 2) * pos - 2); }, easeInCubic : function (pos) { return Math.pow(pos, 3); }, easeOutCubic : function (pos) { return (Math.pow(( pos - 1), 3) + 1); }, easeInOutCubic : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 3); return 0.5 * (Math.pow(( pos - 2), 3) + 2); }, easeInQuart : function (pos) { return Math.pow(pos, 4); }, easeOutQuart : function (pos) { return -(Math.pow(( pos - 1), 4) - 1) }, easeInOutQuart : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 4); return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2); }, easeInQuint : function (pos) { return Math.pow(pos, 5); }, easeOutQuint : function (pos) { return (Math.pow(( pos - 1), 5) + 1); }, easeInOutQuint : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 5); return 0.5 * (Math.pow(( pos - 2), 5) + 2); }, easeInSine : function (pos) { return -Math.cos(pos * (Math.PI / 2)) + 1; }, easeOutSine : function (pos) { return Math.sin(pos * (Math.PI / 2)); }, easeInOutSine : function (pos) { return (-.5 * (Math.cos(Math.PI * pos) - 1)); }, easeInExpo : function (pos) { return (pos == 0) ? 0 : Math.pow(2, 10 * ( pos - 1)); }, easeOutExpo : function (pos) { return (pos == 1) ? 1 : -Math.pow(2, -10 * pos) + 1; }, easeInOutExpo : function (pos) { if (pos == 0) return 0; if (pos == 1) return 1; if ((pos /= 0.5) < 1) return 0.5 * Math.pow(2, 10 * ( pos - 1)); return 0.5 * (-Math.pow(2, -10 * --pos) + 2); }, easeInCirc : function (pos) { return -(Math.sqrt(1 - (pos * pos)) - 1); }, easeOutCirc : function (pos) { return Math.sqrt(1 - Math.pow(( pos - 1), 2)) }, easeInOutCirc : function (pos) { if ((pos /= 0.5) < 1) return -0.5 * (Math.sqrt(1 - pos * pos) - 1); return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1); }, easeOutBounce : function (pos) { if ((pos) < (1 / 2.75)) { return (7.5625 * pos * pos); } else if (pos < (2 / 2.75)) { return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); } else if (pos < (2.5 / 2.75)) { return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); } else { return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); } }, easeInBack : function (pos) { var s = 1.70158; return (pos) * pos * ((s + 1) * pos - s); }, easeOutBack : function (pos) { var s = 1.70158; return ( pos = pos - 1) * pos * ((s + 1) * pos + s) + 1; }, easeInOutBack : function (pos) { var s = 1.70158; if ((pos /= 0.5) < 1) return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)); return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); }, elastic : function (pos) { return -1 * Math.pow(4, -8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1; }, swingFromTo : function (pos) { var s = 1.70158; return ((pos /= 0.5) < 1) ? 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) : 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); }, swingFrom : function (pos) { var s = 1.70158; return pos * pos * ((s + 1) * pos - s); }, swingTo : function (pos) { var s = 1.70158; return (pos -= 1) * pos * ((s + 1) * pos + s) + 1; }, bounce : function (pos) { if (pos < (1 / 2.75)) { return (7.5625 * pos * pos); } else if (pos < (2 / 2.75)) { return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); } else if (pos < (2.5 / 2.75)) { return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); } else { return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); } }, bouncePast : function (pos) { if (pos < (1 / 2.75)) { return (7.5625 * pos * pos); } else if (pos < (2 / 2.75)) { return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); } else if (pos < (2.5 / 2.75)) { return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); } else { return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); } }, easeFromTo : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 4); return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2); }, easeFrom : function (pos) { return Math.pow(pos, 4); }, easeTo : function (pos) { return Math.pow(pos, 0.25); }, linear : function (pos) { return pos }, sinusoidal : function (pos) { return (-Math.cos(pos * Math.PI) / 2) + 0.5; }, reverse : function (pos) { return 1 - pos; }, mirror : function (pos, transition) { transition = transition || tween.sinusoidal; if (pos < 0.5) return transition(pos * 2); else return transition(1 - ( pos - 0.5) * 2); }, flicker : function (pos) { var pos = pos + (Math.random() - 0.5) / 5; return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos); }, wobble : function (pos) { return (-Math.cos(pos * Math.PI * (9 * pos)) / 2) + 0.5; }, pulse : function (pos, pulses) { return (-Math.cos((pos * ((pulses || 5) - .5) * 2) * Math.PI) / 2) + .5; }, blink : function (pos, blinks) { return Math.round(pos * (blinks || 5)) % 2; }, spring : function (pos) { return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none : function (pos) { return 0; } }; |
你会发现有Quad,Cubic,Quart之类的字眼,这些到底是啥意思呢,这里我给出Flash的Tweener 官方文档,一看便知了(如果下面是空白,表示在缓冲,为了看到这么好的演示,耐心等会吧)
根据上述知识点,我的动画函数便出炉了
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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 | /** * @param {HTMLElement/Function} obj 属性时:动画对象; 方法时: 方法引用 * @param {Object} params 属性时:{属性: 结束值}; 方法时:{startArgs: 初始参数[数组], endArgs: 结束参数[数组]}; * @param {Number} duration 动画总时长,单位为毫秒 * @param {String} type 动画类型,默认为linear,具体值参考tween下的属性名 * @param {Function} callback 动画结束时执行的回调函数 */ var animate = function (obj, params, duration, type, callback){ window.requestAnimationFrame = ( function (){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback){ window.setTimeout(callback, 1000 / 60); }; })(); var tween = { linear : function (pos) { return 1; }, easeInQuad : function (pos) { return Math.pow(pos, 2); }, easeOutQuad : function (pos) { return -(Math.pow(( pos - 1), 2) - 1); }, easeInOutQuad : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 2); return -0.5 * ((pos -= 2) * pos - 2); }, easeInCubic : function (pos) { return Math.pow(pos, 3); }, easeOutCubic : function (pos) { return (Math.pow(( pos - 1), 3) + 1); }, easeInOutCubic : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 3); return 0.5 * (Math.pow(( pos - 2), 3) + 2); }, easeInQuart : function (pos) { return Math.pow(pos, 4); }, easeOutQuart : function (pos) { return -(Math.pow(( pos - 1), 4) - 1) }, easeInOutQuart : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 4); return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2); }, easeInQuint : function (pos) { return Math.pow(pos, 5); }, easeOutQuint : function (pos) { return (Math.pow(( pos - 1), 5) + 1); }, easeInOutQuint : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 5); return 0.5 * (Math.pow(( pos - 2), 5) + 2); }, easeInSine : function (pos) { return -Math.cos(pos * (Math.PI / 2)) + 1; }, easeOutSine : function (pos) { return Math.sin(pos * (Math.PI / 2)); }, easeInOutSine : function (pos) { return (-.5 * (Math.cos(Math.PI * pos) - 1)); }, easeInExpo : function (pos) { return (pos == 0) ? 0 : Math.pow(2, 10 * ( pos - 1)); }, easeOutExpo : function (pos) { return (pos == 1) ? 1 : -Math.pow(2, -10 * pos) + 1; }, easeInOutExpo : function (pos) { if (pos == 0) return 0; if (pos == 1) return 1; if ((pos /= 0.5) < 1) return 0.5 * Math.pow(2, 10 * ( pos - 1)); return 0.5 * (-Math.pow(2, -10 * --pos) + 2); }, easeInCirc : function (pos) { return -(Math.sqrt(1 - (pos * pos)) - 1); }, easeOutCirc : function (pos) { return Math.sqrt(1 - Math.pow(( pos - 1), 2)) }, easeInOutCirc : function (pos) { if ((pos /= 0.5) < 1) return -0.5 * (Math.sqrt(1 - pos * pos) - 1); return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1); }, easeOutBounce : function (pos) { if ((pos) < (1 / 2.75)) { return (7.5625 * pos * pos); } else if (pos < (2 / 2.75)) { return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); } else if (pos < (2.5 / 2.75)) { return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); } else { return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); } }, easeInBack : function (pos) { var s = 1.70158; return (pos) * pos * ((s + 1) * pos - s); }, easeOutBack : function (pos) { var s = 1.70158; return ( pos = pos - 1) * pos * ((s + 1) * pos + s) + 1; }, easeInOutBack : function (pos) { var s = 1.70158; if ((pos /= 0.5) < 1) return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)); return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); }, elastic : function (pos) { return -1 * Math.pow(4, -8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1; }, swingFromTo : function (pos) { var s = 1.70158; return ((pos /= 0.5) < 1) ? 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) : 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); }, swingFrom : function (pos) { var s = 1.70158; return pos * pos * ((s + 1) * pos - s); }, swingTo : function (pos) { var s = 1.70158; return (pos -= 1) * pos * ((s + 1) * pos + s) + 1; }, bounce : function (pos) { if (pos < (1 / 2.75)) { return (7.5625 * pos * pos); } else if (pos < (2 / 2.75)) { return (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); } else if (pos < (2.5 / 2.75)) { return (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); } else { return (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); } }, bouncePast : function (pos) { if (pos < (1 / 2.75)) { return (7.5625 * pos * pos); } else if (pos < (2 / 2.75)) { return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + .75); } else if (pos < (2.5 / 2.75)) { return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + .9375); } else { return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + .984375); } }, easeFromTo : function (pos) { if ((pos /= 0.5) < 1) return 0.5 * Math.pow(pos, 4); return -0.5 * ((pos -= 2) * Math.pow(pos, 3) - 2); }, easeFrom : function (pos) { return Math.pow(pos, 4); }, easeTo : function (pos) { return Math.pow(pos, 0.25); }, linear : function (pos) { return pos }, sinusoidal : function (pos) { return (-Math.cos(pos * Math.PI) / 2) + 0.5; }, reverse : function (pos) { return 1 - pos; }, mirror : function (pos, transition) { transition = transition || tween.sinusoidal; if (pos < 0.5) return transition(pos * 2); else return transition(1 - ( pos - 0.5) * 2); }, flicker : function (pos) { var pos = pos + (Math.random() - 0.5) / 5; return tween.sinusoidal(pos < 0 ? 0 : pos > 1 ? 1 : pos); }, wobble : function (pos) { return (-Math.cos(pos * Math.PI * (9 * pos)) / 2) + 0.5; }, pulse : function (pos, pulses) { return (-Math.cos((pos * ((pulses || 5) - .5) * 2) * Math.PI) / 2) + .5; }, blink : function (pos, blinks) { return Math.round(pos * (blinks || 5)) % 2; }, spring : function (pos) { return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none : function (pos) { return 0; } }; animate = function (obj, params, duration, type, callback){ var startValue = {}, //初始值 changeValue = null , //总变化量 startTime = new Date().getTime(), ease = tween[type || 'linear' ]; if ( typeof obj !== 'function' ){ obj = obj.style; for ( var name in params){ startValue[name] = getStyle(obj, name); } changeValue = {}; for (name in params){ changeValue[name] = params[name] - startValue[name]; } } else { changeValue = []; var startArgs = params.startArgs, endArgs = params.endArgs, toString = Object.prototype.toString; for ( var i = 0, len = startArgs.length; i < len; i++){ changeValue[i] = endArgs[i] - startArgs[i]; } } var run = function (){ var timeStamp = new Date().getTime() - startTime, factor = ease(timeStamp / duration); //增量系数 if (!(toString.call(changeValue) === '[object Array]' )){ for (name in params){ obj[name] = (changeValue[name] * factor + startValue[name]) + 'px' ; //套公式吧,亲 } } else { var curArgs = []; for (i = 0; i < len; i++){ curArgs[i] = changeValue[i] * factor + startArgs[i]; } obj.apply( null , curArgs); } if (factor < 1){ window.requestAnimationFrame(run); } else { if (callback)callback(); } }; window.requestAnimationFrame(run); } animate(obj, params, duration, type, callback); }; var getStyle = function (el, style){ var value = null ; if (window.defaultView){ value = window.getComputedStyle(el, null ).getPropertyValue(style); } else { style = style.replace(/\-(\w)/g, function ($0, $1){ return $1.toUpperCase(); }); value = el.currentStyle[style]; if (value === 'auto' ){ value = '0' ; } } return parseFloat(value) || 0; }; |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架