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);
posted @ 2022-05-28 14:44  鸡儿er  阅读(145)  评论(0编辑  收藏  举报