回答編集履歴
1
playground の追加
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
|
+
```
|