1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
|
---
title: 'Cooperative asynchronous JavaScript: Timeouts and intervals'
slug: Learn/JavaScript/Asynchronous/Timeouts_and_intervals
translation_of: Learn/JavaScript/Asynchronous/Timeouts_and_intervals
---
<div>{{LearnSidebar}}</div>
<div>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</div>
<p class="summary">이 장에서는 자바스크립트가 설정된 시간이 경과하거나 혹은 정해진 시간 간격(예 : 초당 설정된 횟수)으로 비동기 코드를 작동하는 전형적인 방법을 살펴본다. 그리고 이 방법들이 어떤 것에 유용한지 얘기해 보고, 그 본질적인 문제에 대해 살펴본다.</p>
<table class="learn-box standard-table">
<tbody>
<tr>
<th scope="row">Prerequisites:</th>
<td>Basic computer literacy, a reasonable understanding of JavaScript fundamentals.</td>
</tr>
<tr>
<th scope="row">Objective:</th>
<td>To understand asynchronous loops and intervals and what they are useful for.</td>
</tr>
</tbody>
</table>
<h2 id="Introduction">Introduction</h2>
<p>오랜 시간 동안 웹플랫폼은 자바스크립트 프로그래머가 일정한 시간이 흐른 뒤에 비동기적 코드를 실행할 수 있게하는 다양한 함수들을 제공해 왔다. 그리고 프로그래머가 중지시킬 때까지 코드 블록을 반복적으로 실행하기 위한 다음과 같은 함수들이 있다.</p>
<dl>
<dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout">setTimeout()</a></code></dt>
<dd>특정 시간이 경과한 뒤에 특정 코드블록을 한번 실행한다.</dd>
<dt></dt>
<dt><code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">setInterval()</a></code></dt>
<dd>각각의 호출 간에 일정한 시간 간격으로 특정 코드블록을 반복적으로 실행한다.</dd>
<dt><code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code></dt>
<dd>setInterval()의 최신 버전; 그 코드가 실행되는 환경에 관계없이 적절한 프레임 속도로 애니메이션을 실행시키면서, 브라우저가 다음 화면을 보여주기 전에 특정 코드블록을 실행한다.</dd>
</dl>
<p>이 함수들에 의해 설정된 비동기 코드는 메인 스레드에서 작동한다. 그러나 프로세서가 이러한 작업을 얼마나 많이 수행하는지에 따라, 코드가 반복되는 중간에 다른 코드를 어느 정도 효율적으로 실행할 수 있다. 어쨌든 이러한 함수들은 웹사이트나 응용 프로그램에서 일정한 애니메이션 및 기타 배경 처리를 실행하는 데 사용된다. 다음 섹션에서는 그것들을 어떻게 사용할 수 있는지 보여줄 것이다.</p>
<h2 id="setTimeout">setTimeout()</h2>
<p>앞에서 언급했듯이 setTimeout ()은 지정된 시간이 경과 한 후 특정 코드 블록을 한 번 실행한다. 그리고 다음과 같은 파라미터가 필요하다.:</p>
<ul>
<li>실행할 함수 또는 다른 곳에 정의 된 함수 참조.</li>
<li>코드를 실행하기 전에 대기 할 밀리세컨드 단위의 시간 간격 (1000밀리세컨드는 1 초)을 나타내는 숫자. 값을 0으로 지정하면(혹은 이 값을 모두 생략하면) 함수가 즉시 실행된다. 왜 이 파라미터를 수행해야 하는지도 좀 더살펴 볼 것이다. </li>
<li>함수가 실행될 때 함수에 전달해야할 파라미터를 나타내는 0이상의 값.</li>
</ul>
<div class="blockIndicator note">
<p><strong>Note:</strong> 타임아웃 콜백은 단독으로 실행되지 않기 때문에 지정된 시간이 지난 그 시점에 정확히 콜백 될 것이라는 보장은 없다. 그보다는 최소한 그 정도의 시간이 지난 후에 호출된다. 메인 스레드가 실행해야 할 핸들러를 찾기 위해 이런 핸들러들을 살펴보는 시점에 도달할 때까지 타임아웃 핸들러를 실행할 수 없다.</p>
</div>
<p>아래 예제에서 브라우저는 2초가 지나면 익명의 함수를 실행하고 경보 메시지를 띄울 것이다. (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see it running live</a>, and <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-settimeout.html">see the source code</a>):</p>
<pre class="brush: js">let myGreeting = setTimeout(function() {
alert('Hello, Mr. Universe!');
}, 2000)</pre>
<p>지정한 함수가 꼭 익명일 필요는 없다. 함수에 이름을 부여 할 수 있고, 다른 곳에서 함수를 정의하고 setTimeout ()에 참조(reference)를 전달할 수도 있다. 아래 코드는 위의 코드와 같은 실행 결과를 얻을 수 있다. </p>
<pre class="brush: js">// With a named function
let myGreeting = setTimeout(function sayHi() {
alert('Hello, Mr. Universe!');
}, 2000)
// With a function defined separately
function sayHi() {
alert('Hello Mr. Universe!');
}
let myGreeting = setTimeout(sayHi, 2000);</pre>
<p>예를 들자면 timeout 함수와 이벤트에 의해 중복 호출되는 함수를 사용하려면 이 방법이 유용할 수 있다. 이 방법은 코드라인을 깔끔하게 정리하는 데 도움을 준다. 특히 timeout 콜백의 코드라인이 여러 줄이라면 더욱 그렇다. </p>
<p>setTimeout ()은 나중에 타임아웃을 할 경우에 타임아웃을 참조하는데 사용하는 식별자 값을 리턴한다. 그 방법을 알아 보려면 아래{{anch("Clearing timeouts")}}을 참조하세요.</p>
<h3 id="setTimeout_함수에_매개변수parameter_전달">setTimeout () 함수에 매개변수(parameter) 전달</h3>
<p>setTimeout () 내에서 실행되는 함수에 전달하려는 모든 매개변수는 setTimeout () 매개변수 목록 끝에 추가하여 전달해야 한다. 아래 예제처럼, 이전 함수를 리팩터링하여 전달된 매개변수의 사람 이름이 추가된 문장을 표시할 수 있다.</p>
<pre class="brush: js">function sayHi(who) {
alert('Hello ' + who + '!');
}</pre>
<p>Say hello의 대상이 되는 사람이름은 <code>setTimeout()의 세번째 매개변수로 함수에 전달된다.</code></p>
<pre class="brush: js">let myGreeting = setTimeout(sayHi, 2000, 'Mr. Universe');</pre>
<h3 id="timeout_취소">timeout 취소</h3>
<p>마지막으로 타임아웃이 생성되면(setTimeout()이 실행되면) 특정시간이 경과하기 전에 <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout">clearTimeout()</a></code>을 호출하여 타임아웃을 취소할 수 있다. <code><a href="/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout">clearTimeout()</a></code>은 <code>setTimeout()</code>콜의 식별자를 매개변수로 <code>setTimeout()</code>에 전달한다. 위 예제의 타임아웃을 취소하려면 아래와 같이 하면 된다.</p>
<pre class="brush: js">clearTimeout(myGreeting);</pre>
<div class="blockIndicator note">
<p><strong>Note</strong>: 인사를 할 사람의 이름을 설정하고 별도의 버튼을 사용하여 인사말을 취소 할 수있는 약간 더 복잡한 폼양식 예제인 <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/greeter-app.html">greeter-app.html</a> 을 참조하세요.</p>
</div>
<h2 id="setInterval">setInterval()</h2>
<p>setTimeout ()은 일정 시간이 지난 후 코드를 한 번 실행해야 할 때 완벽하게 작동합니다. 그러나 애니메이션의 경우와 같이 코드를 반복해서 실행해야 할 경우 어떨까요?</p>
<p>이럴 경우에 setInterval ()이 필요합니다. setInterval ()은 setTimeout ()과 매우 유사한 방식으로 작동합니다. 다만 setTimeout ()처럼 첫 번째 매개 변수(함수)가 타임아웃 후에 한번 실행되는게 아니라 두 번째 매개 변수에 주어진 시간까지 반복적으로 실행되는 것이 차이점입니다. setInterval() 호출의 후속 파라미터로 실행 중인 함수에 필요한 파라미터를 전달할 수도 있다.</p>
<p>예를 들어 봅시다. 다음 함수는 새 Date() 객체를 생성한 후에 ToLocaleTimeString()을 사용하여 시간데이터를 문자열로 추출한 다음 UI에 표시합니다. 그리고 setInterval()을 사용하여 초당 한 번 함수(displayTime)를 실행하면 초당 한 번 업데이트되는 디지털 시계와 같은 효과를 만들어냅니다.(<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see this live</a>, and also <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">see the source</a>): </p>
<pre class="brush: js">function displayTime() {
let date = new Date();
let time = date.toLocaleTimeString();
document.getElementById('demo').textContent = time;
}
const createClock = setInterval(displayTime, 1000);</pre>
<p>setTimeout()과 같이 setInterval()도 식별자 값을 리턴하여 나중에 interval을 취소해야 할 때 사용한다.</p>
<h3 id="interval_취소">interval 취소</h3>
<p>setInterval ()은 아무 조치를 취하지 않으면 끊임없이 계속 실행됩니다. 이 상태를 중지하는 방법이 필요합니다. 그렇지 않으면 브라우저가 추가 작업을 완료 할 수 없거나, 현재 처리 중인 애니메이션이 완료되었을 때 오류가 발생할 수 있습니다. setTimeout()과 같은 방식으로 setInterval () 호출에 의해 반환 된 식별자를 clearInterval () 함수에 전달하여 이 작업을 취소할수 있습니다.</p>
<pre class="brush: js">const myInterval = setInterval(myFunction, 2000);
clearInterval(myInterval);</pre>
<h4 id="능동학습_자신만의_스톱워치를_만들기.">능동학습 : 자신만의 스톱워치를 만들기.</h4>
<p>위에서 모두 설명해 드렸으니, 과제를 드리겠습니다. <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-clock.html">setInterval-clock.html</a> 예제를 수정하여 자신만의 간단한 스톱워치를 만들어 보세요.</p>
<p>위에서 설명(디지털 시계)한 것처럼 시간을 표시해야 하고, 이 예제에서는 아래 기능을 추가하세요.</p>
<ul>
<li>스톱워치를 실행시키는 "Start" 버튼. </li>
<li>일시정지나 종료시키는 "Stop" 버튼.</li>
<li>시간을 0으로 설정하는 "Reset" 버튼.</li>
<li>실제 시간을 표시하지 말고 경과된 시간을 초단위로 표시..</li>
</ul>
<p>몇가지 힌트를 드립니다.</p>
<ul>
<li>버튼 마크업은 여러분이 원하는 대로 구성하고 스타일링 하세요. 자바스크립트 버튼 레퍼런스를 가져올 수 있는 후크를 포함한 semantic HTML을 사용하세요.</li>
<li> 0에서 시작하여 상수 루프를 이용하여 1 초 단위로 증가하는 변수를 작성해 보세요.</li>
<li>앞서 설명한 Date () 객체를 사용하지 않고 이 예제를 작성하는 것이 더 쉽지만 정확도는 떨어집니다. 정확히 1000ms 후에 콜백이 시작된다고 보장 할 수는 없기 때문입니다. 보다 정확한 방법은 startTime = Date.now ()를 실행하여 사용자가 시작 버튼을 클릭했을 때 정확한 timestamp을 얻은 다음 Date.now ()-startTime을 실행하여 시간(milliseconds)을 얻어 냅니다.</li>
<li>시, 분, 초 단위의 시간을 별도의 변수를 통해 계산한 다음 각 루프 반복 후에 문자열로 함께 표시해 보세요. 두 번째 카운터에서 하나씩 해결할 수 있습니다.</li>
<li>어떻게 계산할 것인지 생각해 봅시다.
<ul>
<li>1시간은 3600초 입니다.</li>
<li>분단위 시간은 시간을 60으로 나눈 값에서 시간단위를 제거한 값입니다. 그리고 초단위 시간은 그 값에서 분단위 시간을 제거한 값입니다(예 :3700초를 60초로 나누면 3600초(시단위) + 100초(분단위), 분단위를 다시 60으로 나누면 60초(분단위) + 40초(초단위))</li>
</ul>
</li>
<li>시간값이 10밀리세컨드이하면 화면의 시간을 0으로 표시하세요. 더 일반적인 시계처럼 보일 겁니다.</li>
<li>스톱워치를 정지시키기 위해 interval을 취소해 보세요. 스톱워치를 리셋하기 위해 시간 카운터를 0으로 설정하고, interval을 취소한 후에 화면을 즉시 업데이트 하세요.</li>
<li>스타트 버튼이 클릭되면 버튼을 즉시 비활성화 시키고 스톱/리셋버튼이 클릭되면 다시 활성화해 보세요. 그렇지 않고 스타트 버튼이 여러번 클릭되면 setInterval()이 중복 실행되어서 실행 오류가 발생할 수 있습니다.</li>
</ul>
<div class="blockIndicator note">
<p><strong>Note</strong>: If you get stuck, you can <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">find our version here</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/setinterval-stopwatch.html">source code</a> also).</p>
</div>
<h2 id="setTimeout과_setInterval에서_주의해야할_것_들">setTimeout()과 setInterval()에서 주의해야할 것 들</h2>
<p>setTimeout()과 setInterval()에는 몇가지 주의해야 할 것들이 있습니다. 어떤 것인지 한번 살펴보겠습니다.</p>
<h3 id="순환_timeouts">순환 timeouts</h3>
<p>setTimeout()을 사용하는 또 다른 방법입니다. 바로 setInterval()을 사용하는 대신 setTimeout()을 이용해 같은코드를 반복적으로 실행시키는 방법입니다.</p>
<p>The below example uses a recursive <code>setTimeout()</code> to run the passed function every 100 milliseconds: 아래 예제에서는 setTimeout()이 주어진 함수를 100밀리세컨드마다 실행합니다.</p>
<pre class="brush: js">let i = 1;
setTimeout(function run() {
console.log(i);
i++;
setTimeout(run, 100);
}, 100);</pre>
<p>위 예제를 아래 예제와 비교해 보세요. 아래 예제는 setInterval()을 사용하여 같은 결과를 얻을 수 있습니다.</p>
<pre class="brush: js">let i = 1;
setInterval(function run() {
console.log(i);
i++
}, 100);</pre>
<h4 id="그렇다면_순환_setTimeout과_setInterval은_어떻게_다를까요">그렇다면 순환 setTimeout()과 setInterval()은 어떻게 다를까요?</h4>
<p>두 방법의 차이는 미묘합니다.</p>
<ul>
<li>순환 setTimeout ()은 실행과 실행 사이에 동일한 지연을 보장합니다. 위의 경우 100ms입니다. 코드가 실행 된 후 다시 실행되기 전에 100 ms 동안 대기하므로 간격은 코드 실행 시간에 관계없이 동일합니다.</li>
<li>setInterval ()을 사용하는 예제는 약간 다르게 작동합니다. 설정된 간격에는 실행하려는 코드를 실행하는 데 걸리는 시간이 포함됩니다. 코드를 실행하는 데 40ms가 걸리다면 간격이 60ms에 불과합니다.</li>
<li>setTimeout ()을 재귀적으로 사용할 때 각 반복은 다음 반복을 실행하기 전에 또 하나의 대기시간을 설정합니다. 즉, setTimeout ()의 두 번째 매개 변수의 값은 코드를 다시 실행하기 전에 대기 할 또 하나의 시간을 지정합니다.</li>
</ul>
<p>코드가 지정한 시간 간격보다 실행 시간이 오래 걸리면 순환 setTimeout ()을 사용하는 것이 좋습니다. 이렇게하면 코드 실행 시간에 관계없이 실행 간격이 일정하게 유지되어 오류가 발생하지 않습니다.</p>
<h3 id="즉시_timeouts">즉시 timeouts</h3>
<p>setTimeout()의 값으로 0을 사용하면 메인 코드 스레드가 실행된 후에 가능한 한 빨리 지정된 콜백 함수의 실행을 예약할 수 있다.</p>
<p>예를 들어 아래 코드 (<a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/zero-settimeout.html">see it live</a>) 는 "Hello"가 포함된 alert를 출력 한 다음 첫 번째 경고에서 OK를 클릭하자마자 "World"가 포함된 alert를 출력합니다.</p>
<pre class="brush: js">setTimeout(function() {
alert('World');
}, 0);
alert('Hello');</pre>
<p>이것은 모든 메인 스레드의 실행이 완료되자마자 실행되도록 코드 블록을 설정하려는 경우 유용할 할 수 있습니다. 비동기 이벤트 루프에 배치하면 곧바로 실행될 겁니다.</p>
<h3 id="clearTimeout_와_clearInterval의_취소기능">clearTimeout() 와 clearInterval()의 취소기능</h3>
<p>clearTimeout ()과 clearInterval ()은 모두 동일한 entry를 사용하여 대상 메소드(setTimeout () 또는 setInterval ())을 취소합니다. 흥미롭게도 이는 setTimeout () 또는 setInterval ()을 지우는 데 clearTimeout ()과 clearInterval ()메소드 어느 것을 사용해도 무방합니다.</p>
<p>그러나 일관성을 유지하려면 clearTimeout ()을 사용하여 setTimeout () 항목을 지우고 clearInterval ()을 사용하여 setInterval () 항목을 지우십시오. 혼란을 피하는 데 도움이됩니다.</p>
<h2 id="requestAnimationFrame">requestAnimationFrame()</h2>
<p>requestAnimationFrame ()은 브라우저에서 애니메이션을 효율적으로 실행하기 위해 만들어진 특수한 반복 함수입니다. 근본적으로 setInterval ()의 최신 버전입니다. 브라우저가 다음에 디스플레이를 다시 표시하기 전에 지정된 코드 블록을 실행하여 애니메이션이 실행되는 환경에 관계없이 적절한 프레임 속도로 실행될 수 있도록합니다.</p>
<p>setInterval ()을 사용함에 있어 알려진 문제점을 개선하기위해 만들어졌습니다. 예를 들어 장치에 최적화 된 프레임 속도로 실행되지 않는 문제, 때로는 프레임을 빠뜨리는 문제, 탭이 활성 탭이 아니거나 애니메이션이 페이지를 벗어난 경우에도 계속 실행되는 문제 등등이다 . <a href="http://creativejs.com/resources/requestanimationframe/index.html">CreativeJS에서 이에 대해 자세히 알아보십시오.</a></p>
<div class="blockIndicator note">
<p><strong>Note</strong>: requestAnimationFrame() 사용에 관한 예제들은 이 코스의 여러곳에서 찾아볼 수 있습니다. <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a> 와 <a href="/en-US/docs/Learn/JavaScript/Objects/Object_building_practice">Object building practice</a> 의 예제를 찾아 보세요.</p>
</div>
<p>이 메소드는 화면을 다시 표시하기 전에 호출 할 콜백을 인수로 사용합니다. 이것이 일반적인 패턴입니다. 아래는 사용예를 보여줍니다.</p>
<pre class="brush: js">function draw() {
// Drawing code goes here
requestAnimationFrame(draw);
}
draw();</pre>
<p>그 발상은 애니메이션을 업데이트하는 함수 (예 : 스프라이트 이동, 스코어 업데이트, 데이터 새로 고침 등)를 정의한 후 그것을 호출하여 프로세스를 시작하는 것입니다. 함수 블록의 끝에서 매개 변수로 전달 된 함수 참조를 사용하여 requestAnimationFrame ()을 호출하면 브라우저가 다음 화면을 재표시할 때 함수를 다시 호출하도록 지시합니다. 그런 다음 requestAnimationFrame ()을 반복적으로 호출하므로 계속 실행되는 것입니다.</p>
<div class="blockIndicator note">
<p><strong>Note</strong>: 어떤 간단한 DOM 애니메이션을 수행하려는 경우, <a href="/en-US/docs/Web/CSS/CSS_Animations">CSS Animations</a> 은 JavaScript가 아닌 브라우저의 내부 코드로 직접 계산되므로 속도가 더 빠릅니다. 그러나 더 복잡한 작업을 수행하고 DOM 내에서 직접 액세스 할 수 없는 객체(예 :<a href="/en-US/docs/Web/API/Canvas_API">2D Canvas API</a> or <a href="/en-US/docs/Web/API/WebGL_API">WebGL</a> objects)를 포함하는 경우 대부분의 경우 requestAnimationFrame ()이 더 나은 옵션입니다. </p>
</div>
<h3 id="여러분의_애니메이션의_작동속도는_얼마나_빠른가요">여러분의 애니메이션의 작동속도는 얼마나 빠른가요?</h3>
<p>부드러운 애니메이션을 구현은 직접적으로 프레임 속도에 달려 있으며, 프레임속도는 초당 프레임 (fps)으로 측정됩니다. 이 숫자가 높을수록 애니메이션이 더 매끄럽게 보입니다.</p>
<p>일반적으로 화면 재생률는 60Hz이므로 웹 브라우저를 사용할 때 여러분이 설정할 수 있는 가장 빠른 프레임 속도는 초당 60 프레임 (FPS)입니다. 이 속도보다 빠르게 설정하면 과도한 연산이 실행되어 화면이 더듬거리고 띄엄띄엄 표시될 수 있다. 이런 현상을 프레임 손실 또는 쟁크라고 한다. </p>
<p>재생률 60Hz의 모니터에 60FPS를 달성하려는 경우 각 프레임을 렌더링하기 위해 애니메이션 코드를 실행하려면 약 16.7ms(1000/60)가 필요합니다. 그러므로 각 애니메이션 루프를 통과 할 때마다 실행하려고 하는 코드의 양을 염두에 두어야합니다.</p>
<p>requestAnimationFrame()은 불가능한 경우에도 가능한한 60FPS 값에 가까워 지려고 노력합니다. 실제로 복잡한 애니메이션을 느린 컴퓨터에서 실행하는 경우 프레임 속도가 떨어집니다. requestAnimationFrame ()은 항상 사용 가능한 것을 최대한 활용합니다.</p>
<h3 id="requestAnimationFrame이_setInterval_setTimeout과_다른점은">requestAnimationFrame()이 setInterval(), setTimeout()과 다른점은?</h3>
<p>requestAnimationFrame () 메소드가 이전에 살펴본 다른 메소드와 어떻게 다른지에 대해 조금 더 이야기하겠습니다. 위의 코드를 다시 살펴보면;</p>
<pre class="brush: js">function draw() {
// Drawing code goes here
requestAnimationFrame(draw);
}
draw();</pre>
<p>setInterval()을 사용하여 위와 같은 작업을 하는 방법을 살펴봅시다.</p>
<pre class="brush: js">function draw() {
// Drawing code goes here
}
setInterval(draw, 17);</pre>
<p>앞에서언급했듯이 requestAnimationFrame ()은 시간 간격을 지정하지 않습니다. requestAnimationFrame ()은 현재 상황에서 최대한 빠르고 원활하게 실행됩니다. 어떤 이유로 애니메이션이 화면에 표시되지 않으면 브라우저는 그 애니메이션을 실행하는 데 시간을 낭비하지 않습니다.</p>
<p>반면에 setInterval ()은 특정 시간간격을 필요로 합니다. 1000 ms/60Hz 계산을 통해 최종값 16.6에 도달 한 후 반올림(17)했습니다. 이때 반올림하는 것이 좋습니다. 그 이유는 반내림(16)을하면 60fps보다 빠르게 애니메이션을 실행하려고 하게 되지만 애니메이션의 부드러움에 아무런 영향을 미치지 않기 때문입니다. 앞에서 언급했듯이 60Hz가 표준 재생률입니다.</p>
<h3 id="timestamp를_포함하기">timestamp를 포함하기</h3>
<p>requestAnimationFrame() 함수에 전달 된 실제 콜백에는 requestAnimationFrame()이 실행되기 시작한 이후의 시간을 나타내는 timestamp를 매개변수로 제공할 수 있습니다. 장치 속도에 관계없이 특정 시간과 일정한 속도로 작업을 수행할 수 있으므로 유용합니다. 사용하는 일반적인 패턴은 다음과 같습니다.</p>
<pre class="brush: js">let startTime = null;
function draw(timestamp) {
if(!startTime) {
startTime = timestamp;
}
currentTime = timestamp - startTime;
// Do something based on current time
requestAnimationFrame(draw);
}
draw();</pre>
<h3 id="브라우저_지원">브라우저 지원</h3>
<p>requestAnimationFrame()은 setInterval() / setTimeout()보다 좀 더 최신 브라우저에서 지원됩니다. 가장 흥미롭게도 Internet Explorer 10 이상에서 사용할 수 있습니다. 따라서 별도의 코드로 이전 버전의 IE를 지원해야할 필요가 없다면, requestAnimationFrame()을 사용하지 않을 이유가 없습니다.</p>
<h3 id="간단한_예">간단한 예</h3>
<p>지금까지 이론적으로는 충분히 살펴보았습니다. 그러면 직접 requestAnimationFrame() 예제를 작성해 봅시다. 우리는 간단한 "스피너 애니메이션"을 만들 것입니다. 여러분들이 앱 사용중 서버 과부하일 때 이것을 자주 보았을 겁니다.</p>
<div class="blockIndicator note">
<p><strong>Note</strong>: 실제로는 CSS 애니메이션을 사용하여 이러한 종류의 간단한 애니메이션을 실행해야 합니다. 그러나 이러한 종류의 예제는 requestAnimationFrame() 사용법을 보여주는 데 매우 유용하며, 각 프레임에서 게임의 디스플레이를 업데이트하는 것과 같이 좀 더 복잡한 작업을 수행 할 때 이러한 종류의 기술을 사용하는 것이 좋습니다.</p>
</div>
<ol>
<li>
<p>먼저 HTML 템플릿을 <a href="https://github.com/mdn/learning-area/blob/master/html/introduction-to-html/getting-started/index.html">여기</a>에서.가져옵니다.</p>
</li>
<li>
<p><body>안에 빈 <div> 요소를 삽입합니다. 그리고 ↻캐릭터를 그 안에 추가합니다. 이 예제에서 이 원형 화살표가 회전하게됩니다.</p>
</li>
<li>
<p>아래 CSS를 HTML 템플릿에 여러분이 원하는 방식으로 적용하세요. 이 CSS는 페이지 배경을 빨간색으로, <body>의 height를 html height의 100%로 설정합니다. 그리고 <div>를 수직, 수평으로 <body> 중앙에 위치 시킵니다.</p>
<pre class="brush: css">html {
background-color: white;
height: 100%;
}
body {
height: inherit;
background-color: red;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
div {
display: inline-block;
font-size: 10rem;
}</pre>
</li>
<li>
<p></body> 태크 위에 <script>를 추가하세요.</p>
</li>
<li>
<p><script> 안에 아래 자바스크립트 코드를 추가하세요. 여기에서 <div>의 참조를 상수로 저장하고, rotateCount 변수를 0으로 설정하고, 나중에 requestAnimationFrame()의 시작 시간을 저장할 startTime 변수를 null로 설정하고, 그리고 requestAnimationFrame() 콜의 참조를 저장할 초기화하지 않은 rAF 변수를 선언합니다.</p>
<pre class="brush: js">const spinner = document.querySelector('div');
let rotateCount = 0;
let startTime = null;
let rAF;
</pre>
</li>
<li>
<p>그 아래에 draw() 함수를 추가합니다. 이 함수는 timestamp 매개변수를 포함하는 애니메이션 코드 작성에 사용됩니다.</p>
<pre class="brush: js">function draw(timestamp) {
}</pre>
</li>
<li>
<p>draw() 함수안데 다음 코드를 추가합니다. if 조건문으로 startTime이 정의되지 않았다면 startTime을 정의합니다 (루프 반복의 첫번째에만 작동합니다). 그리고 스피너를 회전시키는 rotateCount 변수값을 설정합니다(현재 timestamp는 시작 timestamp를 3으로 나눈 것이라서 그리 빠르지 않습니다).</p>
<pre class="brush: js"> if (!startTime) {
startTime = timestamp;
}
rotateCount = (timestamp - startTime) / 3;
</pre>
</li>
<li>
<p>draw() 함수 안의 이전 코드 아래에 다음 블록을 추가합니다. 이렇게하면 rotateCount 값이 359보다 큰지 확인합니다 (예 : 360, 완전한 원). 그렇다면 값을 모듈러 360 (즉, 값을 360으로 나눌 때 남은 나머지)으로 설정하여 원 애니메이션이 합리적인 낮은 값으로 중단없이 계속 될 수 있습니다. 꼭 이렇게 해야되는 것은 아니지만 "128000도" 같은 값보다는 0~359 도의 값으로 작업하는 것이 더 쉽습니다.</p>
<pre class="brush: js">if (rotateCount > 359) {
rotateCount %= 360;
}</pre>
</li>
<li>다음으로 아래 코드를 추가하세요. 실제 스피너를 회전시키는 코드입니다.
<pre class="brush: js">spinner.style.transform = 'rotate(' + rotateCount + 'deg)';</pre>
</li>
<li>
<p> draw() 함수 제일 아래에 다음 코드를 추가합니다. 이 코드는 모든 작업의 키 포인트입니다. draw() 함수를 매개변수로 가져오는 requestAnimationFrame() 콜을 저장하기 위해 앞에서 정의한 rAF 변수를 설정합니다. 이 코드는 애니메이션을 실행시키고, 가능한한 60fps에 근사하게 draw() 함수를 계속 실행합니다.</p>
<pre class="brush: js">rAF = requestAnimationFrame(draw);</pre>
</li>
</ol>
<div class="blockIndicator note">
<p><strong>Note</strong>: You can find this <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">example live on GitHub</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/simple-raf-spinner.html">source code</a> also).</p>
</div>
<h3 id="requestAnimationFrame_call의_취소">requestAnimationFrame() call의 취소</h3>
<p>cancelAnimationFrame() 메소드를 호출하여 requestAnimationFrame()을 취소할 수 있다. (접두어가 "clear"가 아니고 "cancel"임에 유의) requestAnimationFrame()에 의해서 리턴된 rAF 변수값을 전달받아 취소한다. </p>
<pre class="brush: js">cancelAnimationFrame(rAF);</pre>
<h3 id="Active_learning_Starting_and_stopping_our_spinner">Active learning: Starting and stopping our spinner</h3>
<p>In this exercise, we'd like you to test out the <code>cancelAnimationFrame()</code> method by taking our previous example and updating it, adding an event listener to start and stop the spinner when the mouse is clicked anywhere on the page.</p>
<p>Some hints:</p>
<ul>
<li>A <code>click</code> event handler can be added to most elements, including the document <code><body></code>. It makes more sense to put it on the <code><body></code> element if you want to maximize the clickable area — the event bubbles up to its child elements.</li>
<li>You'll want to add a tracking variable to check whether the spinner is spinning or not, clearing the animation frame if it is, and calling it again if it isn't.</li>
</ul>
<div class="blockIndicator note">
<p><strong>Note</strong>: Try this yourself first; if you get really stuck, check out of our <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">live example</a> and <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/start-and-stop-spinner.html">source code</a>.</p>
</div>
<h3 id="Throttling_a_requestAnimationFrame_animation">Throttling a requestAnimationFrame() animation</h3>
<p>One limitation of <code>requestAnimationFrame()</code> is that you can't choose your frame rate. This isn't a problem most of the time, as generally you want your animation to run as smoothly as possible, but what about when you want to create an old school, 8-bit-style animation?</p>
<p>This was a problem for example in the Monkey Island-inspired walking animation from our <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing Graphics</a> article:</p>
<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}</p>
<p>In this example we have to animate both the position of the character on the screen, and the sprite being shown. There are only 6 frames in the sprite's animation; if we showed a different sprite frame for every frame displayed on the screen by <code>requestAnimationFrame()</code>, Guybrush would move his limbs too fast and the animation would look ridiculous. We therefore throttled the rate at which the sprite cycles its frames using the following code:</p>
<pre class="brush: js">if (posX % 13 === 0) {
if (sprite === 5) {
sprite = 0;
} else {
sprite++;
}
}</pre>
<p>So we are only cycling a sprite once every 13 animation frames. OK, so it's actually about every 6.5 frames, as we update <code>posX</code> (character's position on the screen) by two each frame:</p>
<pre class="brush: js">if(posX > width/2) {
newStartPos = -((width/2) + 102);
posX = Math.ceil(newStartPos / 13) * 13;
console.log(posX);
} else {
posX += 2;
}</pre>
<p>This is the code that works out how to update the position in each animation frame.</p>
<p>The method you use to throttle your animation will depend on your particular code. For example, in our spinner example we could make it appear to move slower by only increasing our <code>rotateCount</code> by one on each frame instead of two.</p>
<h2 id="Active_learning_a_reaction_game">Active learning: a reaction game</h2>
<p>For our final section of this article, we'll create a 2-player reaction game. Here we have two players, one of whom controls the game using the <kbd>A</kbd> key, and the other with the <kbd>L</kbd> key.</p>
<p>When the <em>Start</em> button is pressed, a spinner like the one we saw earlier is displayed for a random amount of time between 5 and 10 seconds. After that time, a message will appear saying "PLAYERS GO!!" — once this happens, the first player to press their control button will win the game.</p>
<p>{{EmbedGHLiveSample("learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html", '100%', 500)}}</p>
<p>Let's work through this.</p>
<ol>
<li>
<p>First of all, download the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game-starter.html">starter file for the app</a> — this contains the finished HTML structure and CSS styling, giving us a game board that shows the two players' information (as seen above), but with the spinner and results paragraph displayed on top of one another. We just have to write the JavaScript code.</p>
</li>
<li>
<p>Inside the empty {{htmlelement("script")}} element on your page, start by adding the following lines of code that define some constants and variables we'll need in the rest of the code:</p>
<pre class="brush: js">const spinner = document.querySelector('.spinner p');
const spinnerContainer = document.querySelector('.spinner');
let rotateCount = 0;
let startTime = null;
let rAF;
const btn = document.querySelector('button');
const result = document.querySelector('.result');</pre>
<p>In order, these are:</p>
<ol>
<li>A reference to our spinner, so we can animate it.</li>
<li>A reference to the {{htmlelement("div")}} element that contains the spinner, used for showing and hiding it.</li>
<li>A rotate count — how much we want to show the spinner rotated on each frame of the animation.</li>
<li>A null start time — will be populated with a start time when the spinner starts spinning.</li>
<li>An uninitialized variable to later store the {{domxref("Window.requestAnimationFrame", "requestAnimationFrame()")}} call that animates the spinner.</li>
<li>A reference to the Start button.</li>
<li>A reference to the results paragraph.</li>
</ol>
</li>
<li>
<p>Next, below the previous lines of code, add the following function. This simply takes two numerical inputs and returns a random number between the two. We'll need this to generate a random timeout interval later on.</p>
<pre class="brush: js">function random(min,max) {
var num = Math.floor(Math.random()*(max-min)) + min;
return num;
}</pre>
</li>
<li>
<p>Next add in the <code>draw()</code> function, which animates the spinner. This is very similar to the version seen in the simple spinner example we looked at earlier:</p>
<pre class="brush: js">function draw(timestamp) {
if(!startTime) {
startTime = timestamp;
}
rotateCount = (timestamp - startTime) / 3;
if(rotateCount > 359) {
rotateCount %= 360;
}
spinner.style.transform = 'rotate(' + rotateCount + 'deg)';
rAF = requestAnimationFrame(draw);
}</pre>
</li>
<li>
<p>Now it is time to set up the initial state of the app when the page first loads. Add the following two lines, which simply hide the results paragraph and spinner container using <code>display: none;</code>.</p>
<pre class="brush: js">result.style.display = 'none';
spinnerContainer.style.display = 'none';</pre>
</li>
<li>
<p>We'll also define a <code>reset()</code> function, which sets the app back to the original state required to start the game again after it has been played. Add the following at the bottom of your code:</p>
<pre class="brush: js">function reset() {
btn.style.display = 'block';
result.textContent = '';
result.style.display = 'none';
}</pre>
</li>
<li>
<p>OK, enough preparation. Let's make the game playable! Add the following block to your code. The <code>start()</code> function calls <code>draw()</code> to start the spinner spinning and display it in the UI, hides the <em>Start</em> button so we can't mess up the game by starting it multiple times concurrently, and runs a <code>setTimeout()</code> call that runs a <code>setEndgame()</code> function after a random interval between 5 and 10 seconds has passed. We also add an event listener to our button to run the <code>start()</code> function when it is clicked.</p>
<pre class="brush: js">btn.addEventListener('click', start);
function start() {
draw();
spinnerContainer.style.display = 'block';
btn.style.display = 'none';
setTimeout(setEndgame, random(5000,10000));
}</pre>
<div class="blockIndicator note">
<p><strong>Note</strong>: You'll see that in this example we are calling <code>setTimeout()</code> without storing the return value (so not <code>let myTimeout = setTimeout(functionName, interval)</code>). This works and is fine, as long as you don't need to clear your interval/timeout at any point. If you do, you'll need to save the returned identifier.</p>
</div>
<p>The net result of the previous code is that when the <em>Start</em> button is pressed, the spinner is shown and the players are made to wait a random amount of time before they are then asked to press their button. This last part is handled by the <code>setEndgame()</code> function, which we should define next.</p>
</li>
<li>
<p>So add the following function to your code next:</p>
<pre class="brush: js">function setEndgame() {
cancelAnimationFrame(rAF);
spinnerContainer.style.display = 'none';
result.style.display = 'block';
result.textContent = 'PLAYERS GO!!';
document.addEventListener('keydown', keyHandler);
function keyHandler(e) {
console.log(e.key);
if(e.key === 'a') {
result.textContent = 'Player 1 won!!';
} else if(e.key === 'l') {
result.textContent = 'Player 2 won!!';
}
document.removeEventListener('keydown', keyHandler);
setTimeout(reset, 5000);
};
}</pre>
<p>Stepping through this:</p>
<ol>
<li>First we cancel the spinner animation with {{domxref("window.cancelAnimationFrame", "cancelAnimationFrame()")}} (it is always good to clean up unneeded processes), and hide the spinner container.</li>
<li>Next we display the results paragraph and set its text content to "PLAYERS GO!!" to signal to the players that they can now press their button to win.</li>
<li>We then attach a <code><a href="/en-US/docs/Web/API/Document/keydown_event">keydown</a></code> event listener to our document — when any button is pressed down, the <code>keyHandler()</code> function is run.</li>
<li>Inside <code>keyHandler()</code>, we include the event object as a parameter (represented by <code>e</code>) — its {{domxref("KeyboardEvent.key", "key")}} property contains the key that was just pressed, and we can use this to respond to specific key presses with specific actions.</li>
<li>We first log <code>e.key</code> to the console, which is a useful way of finding out the <code>key</code> value of different keys you are pressing.</li>
<li>When <code>e.key</code> is "a", we display a message to say that Player 1 won, and when <code>e.key</code> is "l", we display a message to say Player 2 won. Note that this will only work with lowercase a and l — if an uppercase A or L is submitted (the key plus <kbd>Shift</kbd>), it is counted as a different key.</li>
<li>Regardless of which one of the player control keys was pressed, we remove the <code>keydown</code> event listener using {{domxref("EventTarget.removeEventListener", "removeEventListener()")}} so that once the winning press has happened, no more keyboard input is possible to mess up the final game result. We also use <code>setTimeout()</code> to call <code>reset()</code> after 5 seconds — as we explained earlier, this function resets the game back to its original state so that a new game can be started.</li>
</ol>
</li>
</ol>
<p>That's it, you're all done.</p>
<div class="blockIndicator note">
<p><strong>Note</strong>: If you get stuck, check out <a href="https://mdn.github.io/learning-area/javascript/asynchronous/loops-and-intervals/reaction-game.html">our version of the reaction game</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/javascript/asynchronous/loops-and-intervals/reaction-game.html">source code</a> also).</p>
</div>
<h2 id="Conclusion">Conclusion</h2>
<p>So that's it — all the essentials of async loops and intervals covered in one article. You'll find these methods useful in a lot of situations, but take care not to overuse them — since these still run on the main thread, heavy and intensive callbacks (especially those that manipulate the DOM) can really slow down a page if you're not careful.</p>
<p>{{PreviousMenuNext("Learn/JavaScript/Asynchronous/Introducing", "Learn/JavaScript/Asynchronous/Promises", "Learn/JavaScript/Asynchronous")}}</p>
<h2 id="In_this_module">In this module</h2>
<ul>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Concepts">General asynchronous programming concepts</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Introducing">Introducing asynchronous JavaScript</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Timeouts_and_intervals">Cooperative asynchronous JavaScript: Timeouts and intervals</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Promises">Graceful asynchronous programming with Promises</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Async_await">Making asynchronous programming easier with async and await</a></li>
<li><a href="/en-US/docs/Learn/JavaScript/Asynchronous/Choosing_the_right_approach">Choosing the right approach</a></li>
</ul>
|