回答編集履歴

1

playground の追加

2024/04/22 08:47

投稿

juner
juner

スコア142

test CHANGED
@@ -11,3 +11,210 @@
11
11
  https://developer.mozilla.org/ja/docs/Web/API/Web_Animations_API
12
12
 
13
13
  以上。
14
+
15
+ 追伸:
16
+ 参考まで css animation と js animation(Web Animation API) の比較サンプルを追記しておきます。
17
+
18
+ [playground](https://livecodes.io/?x=code/N4IgLglmA2CmIC4QGMDOqAEB6DArTAhgHYQC2BkA9kRqgaQA5wgA0IAJrKsgE4QNUiiEKxAALWAXbCAPKVhgCGZGII9UCgLwAdEAFUAKgDEAtAA5d2AHzaichUqL1YOkADcIsAO4NKPMJbI1GCwRGCuXhDsYGKanB7IsCaR0WIsGBAkkATQJtw5LgCMAHQADJZYVqJiYKTQAIJgYOrC0MQA5q6hgW3orrqiiu2oiADaIgC6bATIkG6wAKLsUH7C5DwA1gCuDKLr27sIoG1E7VsE7fBINXWiQWGhYLLLbsq9qK5oqLo2PLYYGBkYkKVi+MiwwN+-0BYgATFZABYRgC5PQBCZuC4VCaICXm8CH1dIoeJcwMo8UlCj9wS9MQCgfCxJR5jw0RD4do-liZDjkO9XITiaSNCZYZSsNT2dC6aDoBBkBsWRiJZzubyCWoBcgySYAMyi8UcqkQNyYrlG3H4kD4H5K2mQ-DokE2mHw5EKtkc2kqvEfNVEhSC8l6o0051WBlMt0h02vHnevnq-2aoUikBWQ3Gp1Snmy+Xo92Sr0W-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-ADODeajATLACbDLfQAEwy5MAMeRgA+Uw-X3CNixv3LTuX+rdKG0Opsw5BmNZllOwZ-dqpie6HHQC0yBsDk75hYLjbZezWJangjrPwMAJo+xNm7E6BAW5twqpnCYEwsCTBBGgH4NcqbzYLIFNmjAEgqY1BYqUBgAAPGxKQYjJbSzY5YqAtFNgQJkGUpUTAVXC3KGxFUZgbHaFuLYAHQv5QixgOrsBQjm1qeDYymA4vuTsQF1V1W5R1ZdgBtcbg1C7iCyFsLfg7Om3PHM3rJ8ABUjTMCAA49FEGBVtYA1kskwhRBuo2G7V+r42MCTcFjNuxTXws8Gwwh6ga4SuSCKnBeCpBMCZEiSEEqK48Fbl0TEtbG34vyOZLt-bkDDuwgQOlIqcWARndGw19gE2pu3aUfd+bZa-2vcyO9jAn3SjfYyDhPC-2iCA9psDx8oPYdjW1P1pRJ3wao4uxjq7WPgt3bm49-Hz2iCE9KmoEnrFyfHZpzwcgdOHwhEZw5mpToAACBMIpcAsQr6iQaRxivZ-Oa8y5ZeRZ1-uKpSpAqUEN1eRcJvVza90c+KxH5Osq4NFgTz3mDTe9gCGLA63ABCDA+wAUQyACLUwAQ-K0sALoMuTABrcrkwAnQzMsftDnzfmSRBqquwfxcW8sFYQHVqINji-sBMCERgbQQj3aUhhDAmyiBNYeGEBvmRm-8zAKXvg5eCCu0oCYIc4WvBm68KXn6a5YQpfS4t6WPX0jLcNwXtohXWs9+71EYL-fB-D9Hxv8vW4R8k+wBgI77u5-iTi2X2vpARck5sQAMgwEddbyO1+b7C3X2CSuppdZnIvq-HvG-O-WER-Z-XbQ3AEa-T-W-SfH-I2D3WwFzZsbsEAU8EAP3RAY4Doc4S4YQL4O4YIR4YQNAtgVzAQLAkAE4M4C4K4S0AgSbcgp4NgXKYgpAUgjAlArgCQBQEYBAUYKYDA3gNzPggQlg9AXZLgBQEgtgUVRIdAPwUQwQxSPsMVHSJoTIYYLA9AsgXwfwPg4AdA5sVsAwowrgMAAwqgnA2g4QYwrgYQiglgogsIEg9ApkSmagYQWEAAdkGEoHyksNCGFjgGkCQByGgFEBmDmDoN7HyngDIJ3C2D4JQHCw0GkGPGPCAA)
19
+
20
+ ```html
21
+
22
+ <div class="css">
23
+ <h1>css</h1>
24
+ <h2>表示時</h2>
25
+ <div class="target case-1"></div>
26
+ <h2>hover時</h2>
27
+ <div class="target case-2"></div>
28
+ <h2>click時</h2>
29
+ <div class="target case-3"></div>
30
+ </div>
31
+ <div class="js">
32
+ <h1>js</h1>
33
+ <h2>表示時</h2>
34
+ <div class="target case-1"></div>
35
+ <h2>hover時</h2>
36
+ <div class="target case-2"></div>
37
+ <h2>click時</h2>
38
+ <div class="target case-3"></div>
39
+ </div>
40
+ <script type=module>
41
+ const keyframes = [
42
+ { transform: 'rotate(0)'},
43
+ { transform: 'rotate(1turn)'},
44
+ ];
45
+ const options = {
46
+ duration: 1000,
47
+ iterations: Infinity,
48
+ };
49
+ const onetime = {
50
+ duration: 1000,
51
+ iterations: 1,
52
+ };
53
+ {
54
+ // js 表示時
55
+ const target = document.querySelector(".js .target.case-1");
56
+ target.classList.add('animate');
57
+ target.animate(keyframes, options);
58
+ }
59
+ {
60
+ // js hover時
61
+ let controller = new AbortController();
62
+ const target = document.querySelector(".js .target.case-2");
63
+ target.addEventListener('pointerover', (e) => {
64
+ const target = e.currentTarget;
65
+ target.setPointerCapture(e.pointerId);
66
+ target.classList.add('animate');
67
+ const animate = target.animate(keyframes, options);
68
+ const signal = controller.signal;
69
+ signal.addEventListener('abort', () => {
70
+ target.classList.remove('animate');
71
+ animate.cancel();
72
+ })
73
+ });
74
+ target.addEventListener('pointerout', (e) => {
75
+ controller.abort();
76
+ controller = new AbortController();
77
+ });
78
+ }
79
+ {
80
+ // js click時
81
+ let controller = new AbortController();
82
+ const target = document.querySelector(".js .target.case-3");
83
+ target.addEventListener('pointerup', async (e) => {
84
+ const target = e.currentTarget;
85
+
86
+ // 前をキャンセル
87
+ controller.abort();
88
+ const c = new AbortController();
89
+ controller = c;
90
+
91
+ const animate = target.animate(keyframes, onetime);
92
+ const signal = c.signal;
93
+ // キャンセルすると状態をクリアする
94
+ signal.addEventListener('abort', () => {
95
+ target.classList.remove('animate');
96
+ if (animate.playState === "finished") return;
97
+ animate.cancel();
98
+ });
99
+ target.classList.add('animate');
100
+ try {
101
+ await animate.finished;
102
+ }catch(e){ }
103
+ // 完了したので状態をクリアする
104
+ c.abort();
105
+ });
106
+ }
107
+ {
108
+ // css click時
109
+ let controller = new AbortController();
110
+ const target = document.querySelector(".css .target.case-3");
111
+ let promise = Promise.resolve();
112
+ let i = 0;
113
+ target.addEventListener('pointerup', (e) => {
114
+ const target = e.currentTarget;
115
+
116
+ // 前をキャンセル
117
+ controller.abort();
118
+ const c = new AbortController();
119
+ controller = c;
120
+
121
+ // シーケンシャルに実行しないと タイミングに問題が出る
122
+ promise = promise.then(async () => {
123
+ target.classList.add('animate');
124
+ const signal = c.signal;
125
+
126
+ // キャンセル時は class を外すだけ
127
+ signal.addEventListener('abort', () => {
128
+ target.classList.remove('animate');
129
+ });
130
+
131
+ const deferred = (() => {
132
+ let resolve,reject;
133
+ const promise = new Promise((res, rej) => [resolve, reject] = [res,rej]);
134
+ return {resolve, reject, promise};
135
+ })();
136
+
137
+ // animationend / animationcancel どちらかが発生するまで待機
138
+ target.addEventListener('animationend', deferred.resolve);
139
+ target.addEventListener('animationcancel', deferred.resolve);
140
+ await deferred.promise;
141
+
142
+ // キャンセルしていなければ キャンセル
143
+ if (!signal.aborted) c.abort();
144
+ // イベントの解放
145
+ target.removeEventListener('animationend', deferred.resolve);
146
+ target.removeEventListener('animationcancel', deferred.resolve);
147
+ });
148
+ });
149
+ }
150
+ </script>
151
+ <style>
152
+ :root {
153
+ --animate-color: red;
154
+ .target {
155
+ height: 100px;
156
+ width: 100px;
157
+ display:inline-block;
158
+ background-color: green;
159
+ }
160
+ .js {
161
+ .animate {
162
+ background: var(--animate-color);
163
+ }
164
+ }
165
+ .css {
166
+ /* css 常時 */
167
+ .case-1 {
168
+ background: var(--animate-color);
169
+ animation: linear 1000ms infinite normal rotate;
170
+ }
171
+ /* css hover時 */
172
+ .case-2:hover {
173
+ background: var(--animate-color);
174
+ animation: linear 1000ms infinite normal rotate;
175
+ }
176
+ .case-3.animate {
177
+ background: var(--animate-color);
178
+ animation: linear 1000ms 1 normal rotate;
179
+ }
180
+ }
181
+ }
182
+ @keyframes rotate {
183
+ from {
184
+ transform: rotate(0);
185
+ }
186
+
187
+ to {
188
+ transform: rotate(1turn);
189
+ }
190
+ }
191
+ </style>
192
+ <style>
193
+ /* あまり関係ないその他のスタイル */
194
+ :root {
195
+ body {
196
+ display:grid;
197
+ grid-template-columns: min-content min-content;
198
+ grid-auto-flow: row;
199
+ gap: 20px;
200
+ }
201
+ .js, .css {
202
+ display: grid;
203
+ grid-auto-flow: row;
204
+ grid-row: 1 / -1;
205
+ }
206
+ .js {
207
+ grid-column: 1;
208
+ & > * {
209
+ grid-column: 1;
210
+ }
211
+ }
212
+ .css {
213
+ grid-column: 2;
214
+ & > * {
215
+ grid-column: 2;
216
+ }
217
+ }
218
+ }
219
+ </style>
220
+ ```