canvas繪製動畫的技巧

方帥發表於2021-06-29

我們拿下圖中的沿著線段軌跡移動的原點來舉例,怎麼來實現這個動畫!

1)定義路徑集合Path,裡面規定關鍵座標點如startPoint和endPoint,設定從startPoint移動到endPoint的時間duration。

如下json物件,我們有6段路徑,分別進行了定義。我們將下面這個列表集合命名為path。

 1 [
 2     {
 3         "startPoint": {
 4             "x": 252.86249999999995,
 5             "y": 191.39166666666665
 6         },
 7         "endPoint": {
 8             "x": 252.86249999999995,
 9             "y": 169.66666666666666
10         },
11         "duration": 3000
12     },
13     {
14         "startPoint": {
15             "x": 251.62499999999994,
16             "y": 169.66666666666666
17         },
18         "endPoint": {
19             "x": 393.52499999999986,
20             "y": 226.2833333333333
21         },
22         "duration": 15000
23     },
24     {
25         "startPoint": {
26             "x": 393.52499999999986,
27             "y": 226.2833333333333
28         },
29         "endPoint": {
30             "x": 393.52499999999986,
31             "y": 427.075
32         },
33         "duration": 15000
34     },
35     {
36         "startPoint": {
37             "x": 385.6874999999999,
38             "y": 420.4916666666667
39         },
40         "endPoint": {
41             "x": 385.6874999999999,
42             "y": 407.2916666666667
43         },
44         "duration": 3000
45     },
46     {
47         "startPoint": {
48             "x": 385.6874999999999,
49             "y": 407.2916666666667
50         },
51         "endPoint": {
52             "x": 125.8125,
53             "y": 421.94166666666666
54         },
55         "duration": 15000
56     },
57     {
58         "startPoint": {
59             "x": 126.6375,
60             "y": 421.94166666666666
61         },
62         "endPoint": {
63             "x": 126.6375,
64             "y": 434.31666666666666
65         },
66         "duration": 3000
67     }
68 ]

2)每次事件迴圈執行程式碼都會計算一個座標值

怎麼計算新座標?具體就是如下程式碼:

this.curTime += this.timeFreshTime;
let currentX = Easing.Linear(this.curTime, this.movePath.startPoint.x, this.movePath.endPoint.x - this.movePath.startPoint.x, this.movePath.duration);
let currentY = Easing.Linear(this.curTime, this.movePath.startPoint.y, this.movePath.endPoint.y - this.movePath.startPoint.y, this.movePath.duration);

利用當前時間,起點,終點。我們藉助時間曲線easing庫,裡面的計算專為動畫設計,還有我之前介紹過一個運算庫《Tween演算法及緩動效果》都是一樣的。

程式碼參考如下:

canvas繪製動畫的技巧
  1 export class Easing {
  2     // t: current time(當前時間),
  3     // b: beginning value(初始值),
  4     // c: chang in value (變化量),
  5     // d: duration(持續時間)
  6     static Linear = function (t, b, c, d) { return c * t / d + b; };
  7     static Quad = {
  8         easeIn: function (t, b, c, d) {
  9             return c * (t /= d) * t + b;
 10         },
 11         easeOut: function (t, b, c, d) {
 12             return -c * (t /= d) * (t - 2) + b;
 13         },
 14         easeInOut: function (t, b, c, d) {
 15             if ((t /= d / 2) < 1) return c / 2 * t * t + b;
 16             return -c / 2 * ((--t) * (t - 2) - 1) + b;
 17         }
 18     };
 19     static Cubic = {
 20         easeIn: function (t, b, c, d) {
 21             return c * (t /= d) * t * t + b;
 22         },
 23         easeOut: function (t, b, c, d) {
 24             return c * ((t = t / d - 1) * t * t + 1) + b;
 25         },
 26         easeInOut: function (t, b, c, d) {
 27             if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
 28             return c / 2 * ((t -= 2) * t * t + 2) + b;
 29         }
 30     };
 31     static Quart = {
 32         easeIn: function (t, b, c, d) {
 33             return c * (t /= d) * t * t * t + b;
 34         },
 35         easeOut: function (t, b, c, d) {
 36             return -c * ((t = t / d - 1) * t * t * t - 1) + b;
 37         },
 38         easeInOut: function (t, b, c, d) {
 39             if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
 40             return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
 41         }
 42     };
 43     static Quint = {
 44         easeIn: function (t, b, c, d) {
 45             return c * (t /= d) * t * t * t * t + b;
 46         },
 47         easeOut: function (t, b, c, d) {
 48             return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
 49         },
 50         easeInOut: function (t, b, c, d) {
 51             if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
 52             return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
 53         }
 54     };
 55     static Sine = {
 56         easeIn: function (t, b, c, d) {
 57             return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
 58         },
 59         easeOut: function (t, b, c, d) {
 60             return c * Math.sin(t / d * (Math.PI / 2)) + b;
 61         },
 62         easeInOut: function (t, b, c, d) {
 63             return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
 64         }
 65     };
 66     static Expo = {
 67         easeIn: function (t, b, c, d) {
 68             return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
 69         },
 70         easeOut: function (t, b, c, d) {
 71             return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
 72         },
 73         easeInOut: function (t, b, c, d) {
 74             if (t == 0) return b;
 75             if (t == d) return b + c;
 76             if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
 77             return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
 78         }
 79     };
 80     static Circ = {
 81         easeIn: function (t, b, c, d) {
 82             return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
 83         },
 84         easeOut: function (t, b, c, d) {
 85             return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
 86         },
 87         easeInOut: function (t, b, c, d) {
 88             if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
 89             return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
 90         }
 91     };
 92     static Elastic = {
 93         easeIn: function (t, b, c, d, a, p) {
 94             var s;
 95             if (t == 0) return b;
 96             if ((t /= d) == 1) return b + c;
 97             if (typeof p == "undefined") p = d * .3;
 98             if (!a || a < Math.abs(c)) {
 99                 s = p / 4;
100                 a = c;
101             } else {
102                 s = p / (2 * Math.PI) * Math.asin(c / a);
103             }
104             return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
105         },
106         easeOut: function (t, b, c, d, a, p) {
107             var s;
108             if (t == 0) return b;
109             if ((t /= d) == 1) return b + c;
110             if (typeof p == "undefined") p = d * .3;
111             if (!a || a < Math.abs(c)) {
112                 a = c;
113                 s = p / 4;
114             } else {
115                 s = p / (2 * Math.PI) * Math.asin(c / a);
116             }
117             return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
118         },
119         easeInOut: function (t, b, c, d, a, p) {
120             var s;
121             if (t == 0) return b;
122             if ((t /= d / 2) == 2) return b + c;
123             if (typeof p == "undefined") p = d * (.3 * 1.5);
124             if (!a || a < Math.abs(c)) {
125                 a = c;
126                 s = p / 4;
127             } else {
128                 s = p / (2 * Math.PI) * Math.asin(c / a);
129             }
130             if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
131             return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
132         }
133     };
134     static Back = {
135         easeIn: function (t, b, c, d, s) {
136             if (typeof s == "undefined") s = 1.70158;
137             return c * (t /= d) * t * ((s + 1) * t - s) + b;
138         },
139         easeOut: function (t, b, c, d, s) {
140             if (typeof s == "undefined") s = 1.70158;
141             return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
142         },
143         easeInOut: function (t, b, c, d, s) {
144             if (typeof s == "undefined") s = 1.70158;
145             if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
146             return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
147         }
148     };
149     static Bounce = {
150         easeIn: function (t, b, c, d) {
151             return c - Easing.Bounce.easeOut(d - t, 0, c, d) + b;
152         },
153         easeOut: function (t, b, c, d) {
154             if ((t /= d) < (1 / 2.75)) {
155                 return c * (7.5625 * t * t) + b;
156             } else if (t < (2 / 2.75)) {
157                 return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
158             } else if (t < (2.5 / 2.75)) {
159                 return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
160             } else {
161                 return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
162             }
163         },
164         easeInOut: function (t, b, c, d) {
165             if (t < d / 2) {
166                 return Easing.Bounce.easeIn(t * 2, 0, c, d) * .5 + b;
167             } else {
168                 return Easing.Bounce.easeOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
169             }
170         }
171     }
172 }
Easing

3)當前座標點已計算更新,此時在新位置處繪製白色點,我們的事件循序間隔設定的50ms,所有能夠產生平滑移動的效果。

4)實際需要幾個移動的白點,那就需要定義幾個path。如上gif圖中我們有兩個path,這兩個path初始化的時間是錯開的,所有才產生非同步移動的效果。

5)當一個path走完,再讓其從頭走(path的第一個startPoint endPoint),這樣不斷地迴圈下去。

相關文章