js 动画补间 Tween
1 /* RunningList (触发过程中可以安全的删除自己) 2 如果触发过程中删除(回调函数中删除正在遍历的数组), 不仅 len 没有变(遍历前定义的len没有变, 真实的len随之减少), 而且还会漏掉一个key; 3 4 */ 5 class RunningList{ 6 7 static getProxy(runName){ 8 9 return new Proxy(new RunningList(runName), { 10 11 get(tar, key){ 12 13 }, 14 15 set(tar, key, val){ 16 17 } 18 19 }); 20 21 } 22 23 constructor(runName = 'update'){ 24 this._running = false; 25 this._list = []; 26 this._delList = []; 27 this._runName = runName; 28 29 } 30 31 get length(){ 32 return this._list.length; 33 } 34 35 push(v){ 36 this._list.push(v); 37 38 } 39 40 splice(v){ 41 if(this._running === true) this._delList.push(v); 42 43 else{ 44 const i = this._list.indexOf(v); 45 if(i !== -1) this._list.splice(i, 1); 46 } 47 48 } 49 50 update(){ 51 var k, len = this._list.length; 52 53 this._running = true; 54 for(k = 0; k < len; k++) this._list[k][this._runName](); 55 this._running = false; 56 57 len = this._delList.length; 58 for(k = 0; k < len; k++) this.splice(this._delList[k]); 59 this._delList.length = 0; 60 61 } 62 63 } 64 65 66 67 68 /* TweenValue (从 原点 以规定的时间到达 终点) 69 70 parameter: origin, end, time, onUpdate, onEnd; 71 72 attribute: 73 origin: Object; //原点(起点) 74 end: Object; //终点 75 time: Number; //origin 到 end 花费的时间 76 onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null; 77 onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是true将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间) 78 79 method: 80 reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性 81 reverse(): undefined; //this.end 复制 this.origin 的原始值 82 update(): undefined; //Tween 通过此方法统一更新 TweenValue 83 84 demo: 85 //init Tween: 86 const tween = new Tween(), 87 animate = function (){ 88 requestAnimationFrame(animate); 89 tween.update(); 90 } 91 92 //init TweenValue: 93 const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)); 94 95 animate(); 96 tween.start(v1); 97 98 */ 99 class TweenValue{ 100 101 constructor(origin = {}, end = {}, time = 500, onUpdate = null, onEnd = null, onStart = null){ 102 this.origin = origin; 103 this.end = end; 104 this.time = time; 105 106 this.onUpdate = onUpdate; 107 this.onEnd = onEnd; 108 this.onStart = onStart; 109 110 //以下属性不能直接设置 111 this._r = null; 112 this._t = 0; 113 this._v = Object.create(null); 114 115 } 116 117 _start(){ 118 var v = ""; 119 for(v in this.origin) this._v[v] = this.origin[v]; 120 121 this._t = Date.now(); 122 //this.update(); 123 124 } 125 126 reset(origin, end){ 127 this.origin = origin; 128 this.end = end; 129 this._v = Object.create(null); 130 131 } 132 133 reverse(){ 134 var n = ""; 135 for(n in this.origin) this.end[n] = this._v[n]; 136 137 } 138 139 update(){ 140 if(this["_r"] !== null){ 141 142 var ted = Date["now"]() - this["_t"]; 143 144 if(ted >= this["time"]){ 145 146 for(ted in this["origin"]) this["origin"][ted] = this["end"][ted]; 147 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]); 148 149 if(this["onEnd"] !== null){ 150 151 if(this["onEnd"]() !== true){ 152 if(this["_r"] !== null) this["_r"]["stop"](this); 153 } 154 155 else this["_start"](); 156 157 } 158 159 else this["_r"]["stop"](this); 160 161 } 162 163 else{ 164 let n = ""; 165 ted = ted / this["time"]; 166 for(n in this["origin"]) this["origin"][n] = ted * (this["end"][n] - this["_v"][n]) + this["_v"][n]; 167 if(this["onUpdate"] !== null) this["onUpdate"](this["origin"]); 168 169 } 170 171 } 172 173 } 174 175 } 176 177 Object.defineProperties(TweenValue.prototype, { 178 179 isTweenValue: { 180 configurable: false, 181 enumerable: false, 182 writable: false, 183 value: true, 184 } 185 186 }); 187 188 189 190 191 /* TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween) 192 193 demo: 194 const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)), 195 animate = function (){ 196 requestAnimationFrame(animate); 197 v1.update(); 198 } 199 200 animate(); 201 v1.start(); 202 203 */ 204 class TweenAlone extends TweenValue{ 205 206 constructor(origin, end, time, onUpdate, onEnd, onStart){ 207 super(origin, end, time, onUpdate, onEnd, onStart); 208 209 } 210 211 start(){ 212 if(this.onStart !== null) this.onStart(); 213 this._r = this; 214 this._start(); 215 216 } 217 218 stop(){ 219 this._r = null; 220 221 } 222 223 } 224 225 226 227 228 229 /* Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue) 230 231 parameter: 232 attribute: 233 method: 234 start(value: TweenValue): undefined; 235 stop(value: TweenValue): undefined; 236 237 static: 238 Value: TweenValue; 239 240 demo: 241 //init Tween: 242 const tween = new Tween(), 243 animate = function (){ 244 requestAnimationFrame(animate); 245 tween.update(); 246 } 247 248 //init TweenValue: 249 const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => { 250 v2.reverse(); //v2.end 复制起始值 251 return true; //返回true表示不删除队列, 需要继续补间 252 }); 253 254 animate(); 255 tween.start(v2); 256 257 */ 258 class Tween extends RunningList{ 259 260 static Value = TweenValue; 261 262 constructor(){ 263 super(); 264 265 } 266 267 start(value){ 268 if(value.onStart !== null) value.onStart(); 269 if(value._r === null) this.push(value); 270 value._r = this; 271 value._start(this); 272 273 } 274 275 stop(value){ 276 if(value._r !== null) this.splice(value); 277 value._r = null; 278 279 } 280 281 } 282 283 284 285 286 /* TweenTargetChange 朝着轴插值(有效的跟踪动态目标) 287 parameter: 288 v1 = {x: 0}, 289 v2 = {x: 100}, 290 distance = 1, //每次移动的距离 291 onUpdate = null, // 292 onEnd = null 293 294 attribute: 295 v1: Object; //起点 296 v2: Object; //终点 297 onUpdate: Function; // 298 onEnd: Function; // 299 300 method: 301 update(): undefined; //一般在动画循环里执行此方法 302 updateAxis(): undefined; //更新v1至v2的方向轴 (初始化时构造器自动调用一次) 303 setDistance(distance: Number): undefined; //设置每次移动的距离 (初始化时构造器自动调用一次) 304 305 demo: 306 const ttc = new TweenTargetChange({x:0, y:0}, {x:100, y:100}, 10), 307 308 //计时器模拟动画循环函数, 每秒执行一次.update() 309 timer = new Timer(() => { 310 ttc.update(); 311 console.log('update: ', ttc.v1); 312 313 }, 1000, Infinity); 314 315 ttc.onEnd = v => { 316 timer.stop(); 317 console.log('end: ', v); 318 } 319 320 timer.start(); 321 console.log(ttc); 322 323 */ 324 class TweenTargetChange{ 325 326 #distance = 1; 327 #distancePow2 = 1; 328 #axis = {}; 329 330 constructor(v1 = {x: 0}, v2 = {x: 100}, distance, onUpdate = null, onEnd = null){ 331 this.v1 = v1; 332 this.v2 = v2; 333 this.onUpdate = onUpdate; 334 this.onEnd = onEnd; 335 336 this.setDistance(distance); 337 this.updateAxis(); 338 } 339 340 setDistance(v = 1){ 341 this.#distance = v; 342 this.#distancePow2 = Math.pow(v, 2); 343 344 } 345 346 updateAxis(){ //更新轴 347 var n, v, len = 0; 348 349 for(n in this.v1){ 350 v = this.v2[n] - this.v1[n]; 351 len += v * v; 352 this.#axis[n] = v; 353 354 } 355 356 len = Math.sqrt(len); 357 358 if(len !== 0){ 359 360 for(n in this.v1) this.#axis[n] *= 1 / len; 361 362 } 363 364 } 365 366 update(){ 367 var n, len = 0; 368 369 for(n in this.v1) len += Math.pow(this.v1[n] - this.v2[n], 2); 370 371 if(len > this.#distancePow2){ 372 373 for(n in this.v1) this.v1[n] += this.#axis[n] * this.#distance; 374 if(this.onUpdate !== null) this.onUpdate(this.v1); 375 376 } 377 378 else{ 379 for(n in this.v1) this.v1[n] = this.v2[n]; 380 if(this.onEnd !== null) this.onEnd(this.v1); 381 382 } 383 384 } 385 386 }
TweenValue (从 原点 以规定的时间到达 终点)
parameter: origin, end, time, onUpdate, onEnd;
attribute:
origin: Object; //原点(起点)
end: Object; //终点
time: Number; //origin 到 end 花费的时间
onUpdate: Function; //更新回调; 一个回调参数 origin; 默认null;
onEnd: Function; //结束回调; 没有回调参数; 默认null; (如果返回的是true将不从队列删除, 你可以在onEnd中更新.end不间断的继续补间)
method:
reset(origin, end: Object): undefined; //更换 .origin, .end; 它会清除其它对象的缓存属性
reverse(): undefined; //this.end 复制 this.origin 的原始值
update(): undefined; //Tween 通过此方法统一更新 TweenValue
demo:
//init Tween:
const tween = new Tween(),
animate = function (){
requestAnimationFrame(animate);
tween.update();
}
//init TweenValue:
const v1 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v));
animate();
tween.start(v1);
TweenAlone (相对于 TweenValue 此类可以独立补间, 不需要 Tween)
demo:
const v1 = new TweenAlone({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v)),
animate = function (){
requestAnimationFrame(animate);
v1.update();
}
animate();
v1.start();
Tween 动画补间 (TweenValue 的root, 可以管理多个TweenValue)
parameter:
attribute:
method:
start(value: TweenValue): undefined;
stop(value: TweenValue): undefined;
static:
Value: TweenValue;
demo:
//init Tween:
const tween = new Tween(),
animate = function (){
requestAnimationFrame(animate);
tween.update();
}
//init TweenValue:
const v2 = new Tween.Value({x:0, y:0}, {x:5, y:10}, 1000, v => console.log(v), v => {
v2.reverse(); //v2.end 复制起始值
return true; //返回true表示不删除队列, 需要继续补间
});
animate();
tween.start(v2);
TweenTargetChange 朝着轴插值(有效的跟踪动态目标)
parameter:
v1 = {x: 0},
v2 = {x: 100},
distance = 1, //每次移动的距离
onUpdate = null, //
onEnd = null
attribute:
v1: Object; //起点
v2: Object; //终点
onUpdate: Function; //
onEnd: Function; //
method:
update(): undefined; //一般在动画循环里执行此方法
updateAxis(): undefined; //更新v1至v2的方向轴 (初始化时构造器自动调用一次)
setDistance(distance: Number): undefined; //设置每次移动的距离 (初始化时构造器自动调用一次)
demo:
const ttc = new TweenTargetChange({x:0, y:0}, {x:100, y:100}, 10),
//计时器模拟动画循环函数, 每秒执行一次.update()
timer = new Timer(() => {
ttc.update();
console.log('update: ', ttc.v1);
}, 1000, Infinity);
ttc.onEnd = v => {
timer.stop();
console.log('end: ', v);
}
timer.start();
console.log(ttc);