Pengoptimalan Canvas HTML5: Contoh Praktis
() translation by (you can also view the original English article)
Jika Anda telah melakukan pengembangan JavaScript cukup lama, kemungkinan besar Anda akan menggagalkan browser Anda beberapa kali. Masalahnya biasanya ternyata beberapa bug JavaScript, seperti perulangan while
tanpa henti; jika tidak, tersangka berikutnya adalah transformasi halaman atau animasi - jenis yang melibatkan penambahan dan penghapusan elemen dari halaman web atau menganimasi properti gaya CSS. Tutorial ini berfokus pada pengoptimalan animasi yang dihasilkan dengan menggunakan JS dan elemen <canvas>
HTML5
Tutorial ini dimulai dan diakhiri dengan widget animasi HTML5 yang Anda lihat di bawah ini:
Kami akan membawanya bersama kami dalam perjalanan, menjelajahi berbagai kiat dan teknik pengoptimalan canvas dan menerapkannya ke kode sumber widget JavaScript. Tujuannya adalah untuk meningkatkan kecepatan eksekusi widget dan berakhir dengan widget animasi yang lebih halus dan lebih cair, didukung oleh JavaScript yang lebih ramping dan lebih efisien.
Download sumber berisi HTML dan JavaScript dari setiap langkah dalam tutorial, sehingga Anda dapat mengikuti dari manapun juga.
Mari kita ikuti langkah pertama.
Langkah 1: Memainkan Trailer Film
Widget di atas didasarkan pada trailer film untuk Sintel, sebuah film animasi 3D oleh Blender Foundation. Dibuat dengan menggunakan dua tambahan HTML5 yang paling populer: elemen <canvas>
dan <video>
. dan .
<video>
memuat dan memutar file video Sintel, sedangkan <canvas>
menghasilkan urutan animasinya sendiri dengan mengambil snapshot dari video yang diputar dan mencampurnya dengan teks dan grafis lainnya. Saat Anda mengklik untuk memutar video, canvas muncul di latar belakang gelap yang merupakan salinan video hitam dan putih yang lebih besar dari video yang sedang diputar. Screenshot video yang lebih kecil dan berwarna disalin ke layar, dan meluncur di atasnya sebagai bagian dari ilustrasi gulungan film.



Di pojok kiri atas, kita memiliki judul dan beberapa baris teks deskriptif yang memudar masuk dan keluar saat animasi dimainkan. Kecepatan kinerja script dan metrik terkait disertakan sebagai bagian dari animasi, di kotak hitam kecil di pojok kiri bawah dengan grafik dan teks yang jelas. Kita akan melihat item khusus ini secara lebih rinci nanti.
Akhirnya, ada pisau besar berputar yang terbang melintasi layar di awal animasi, yang grafisnya diambil dari file gambar PNG eksternal.
Langkah 2: Melihat Sumbernya
Kode sumber berisi campuran HTML, CSS dan Javascript biasa. HTML-nya jarang: hanya tag <canvas>
dan <video>
, dilampirkan dalam <div>
kontainer:
1 |
<div id="animationWidget" > |
2 |
<canvas width="368" height="208" id="mainCanvas" ></canvas> |
3 |
<video width="184" height="104" id="video" autobuffer="autobuffer" controls="controls" poster="poster.jpg" > |
4 |
<source src="sintel.mp4" type="video/mp4" ></source> |
5 |
<source src="sintel.webm" type="video/webm" ></source> |
6 |
</video>
|
7 |
</div>
|
<div>
kontainer diberikan sebuah ID (animationWidget
), yang bertindak sebagai kait untuk semua aturan CSS yang diterapkan padanya dan isinya (di bawah).
1 |
|
2 |
#animationWidget{ |
3 |
border:1px #222 solid; |
4 |
position:relative; |
5 |
width: 570px; |
6 |
height: 220px; |
7 |
}
|
8 |
#animationWidget canvas{ |
9 |
border:1px #222 solid; |
10 |
position:absolute; |
11 |
top:5px; |
12 |
left:5px; |
13 |
}
|
14 |
#animationWidget video{ |
15 |
position:absolute; |
16 |
top:110px; |
17 |
left:380px; |
18 |
}
|
Sementara HTML dan CSS adalah rempah-rempah dan bumbu yang diasinkan, JavaScript adalah daging dari widget.
- Di bagian atas, kita memiliki objek utama yang akan sering digunakan melalui skrip, termasuk referensi ke elemen canvas dan konteks 2D-nya.
- Fungsi
init()
dipanggil setiap kali video mulai diputar, dan mengatur semua objek yang digunakan dalam skrip. - Fungsi
sampleVideo()
menangkap frame video yang diputar saat ini, sedangkansetBlade()
memuat gambar eksternal yang dibutuhkan oleh animasi. - Kecepatan dan konten animasi canvas dikendalikan oleh fungsi
main()
, yang seperti detak jantung dari skrip. Jalankan pada interval reguler begitu video mulai diputar, ia melukis setiap frame animasi dengan terlebih dahulu membersihkan canvas, lalu memanggil masing-masing dari lima fungsi menggambar dari skrip:drawBackground()
drawFilm()
drawTitle()
drawDescription()
drawStats()
Seperti namanya, setiap fungsi menggambar bertanggung jawab untuk menggambar item di adegan animasi. Penataan kode dengan cara ini meningkatkan fleksibilitas dan mempermudah perawatan di masa depan.
Skrip lengkap ditunjukkan di bawah ini. Luangkan waktu untuk menilainya, dan lihat apakah Anda bisa menemukan perubahan yang akan Anda lakukan untuk mempercepatnya.
1 |
(function(){ |
2 |
if( !document.createElement("canvas").getContext ){ return; } //the canvas tag isn't supported |
3 |
|
4 |
var mainCanvas = document.getElementById("mainCanvas"); // points to the HTML canvas element above |
5 |
var mainContext = mainCanvas.getContext('2d'); //the drawing context of the canvas element |
6 |
var video = document.getElementById("video"); // points to the HTML video element |
7 |
var frameDuration = 33; // the animation's speed in milliseconds |
8 |
video.addEventListener( 'play', init ); // The init() function is called whenever the user presses play & the video starts/continues playing |
9 |
video.addEventListener( 'ended', function(){ drawStats(true); } ); //drawStats() is called one last time when the video end, to sum up all the statistics |
10 |
|
11 |
var videoSamples; // this is an array of images, used to store all the snapshots of the playing video taken over time. These images are used to create the 'film reel' |
12 |
var backgrounds; // this is an array of images, used to store all the snapshots of the playing video taken over time. These images are used as the canvas background |
13 |
var blade; //An canvas element to store the image copied from blade.png |
14 |
var bladeSrc = 'blade.png'; //path to the blade's image source file |
15 |
|
16 |
var lastPaintCount = 0; // stores the last value of mozPaintCount sampled |
17 |
var paintCountLog = []; // an array containing all measured values of mozPaintCount over time |
18 |
var speedLog = []; // an array containing all the execution speeds of main(), measured in milliseconds |
19 |
var fpsLog = []; // an array containing the calculated frames per secong (fps) of the script, measured by counting the calls made to main() per second |
20 |
var frameCount = 0; // counts the number of times main() is executed per second. |
21 |
var frameStartTime = 0; // the last time main() was called |
22 |
|
23 |
// Called when the video starts playing. Sets up all the javascript objects required to generate the canvas animation and measure perfomance
|
24 |
function init(){ |
25 |
if( video.currentTime > 1 ){ return; } |
26 |
|
27 |
bladeSrc = new Image(); |
28 |
bladeSrc.src = "blade.png"; |
29 |
bladeSrc.onload = setBlade; |
30 |
|
31 |
backgrounds = []; |
32 |
videoSamples = []; |
33 |
fpsLog = []; |
34 |
paintCountLog = []; |
35 |
if( window.mozPaintCount ){ lastPaintCount = window.mozPaintCount; } |
36 |
speedLog = []; |
37 |
frameCount = 0; |
38 |
frameStartTime = 0; |
39 |
main(); |
40 |
setTimeout( getStats, 1000 ); |
41 |
}
|
42 |
|
43 |
// As the scripts main function, it controls the pace of the animation
|
44 |
function main(){ |
45 |
setTimeout( main, frameDuration ); |
46 |
if( video.paused || video.ended ){ return; } |
47 |
|
48 |
var now = new Date().getTime(); |
49 |
if( frameStartTime ){ |
50 |
speedLog.push( now - frameStartTime ); |
51 |
}
|
52 |
frameStartTime = now; |
53 |
if( video.readyState < 2 ){ return; } |
54 |
|
55 |
frameCount++; |
56 |
mainCanvas.width = mainCanvas.width; //clear the canvas |
57 |
drawBackground(); |
58 |
drawFilm(); |
59 |
drawDescription(); |
60 |
drawStats(); |
61 |
drawBlade(); |
62 |
drawTitle(); |
63 |
}
|
64 |
|
65 |
// This function is called every second, and it calculates and stores the current frame rate
|
66 |
function getStats(){ |
67 |
if( video.readyState >= 2 ){ |
68 |
if( window.mozPaintCount ){ //this property is specific to firefox, and tracks how many times the browser has rendered the window since the document was loaded |
69 |
paintCountLog.push( window.mozPaintCount - lastPaintCount ); |
70 |
lastPaintCount = window.mozPaintCount; |
71 |
}
|
72 |
|
73 |
fpsLog.push(frameCount); |
74 |
frameCount = 0; |
75 |
}
|
76 |
setTimeout( getStats, 1000 ); |
77 |
}
|
78 |
|
79 |
// create blade, the ofscreen canavs that will contain the spining animation of the image copied from blade.png
|
80 |
function setBlade(){ |
81 |
blade = document.createElement("canvas"); |
82 |
blade.width = 400; |
83 |
blade.height = 400; |
84 |
blade.angle = 0; |
85 |
blade.x = -blade.height * 0.5; |
86 |
blade.y = mainCanvas.height/2 - blade.height/2; |
87 |
}
|
88 |
|
89 |
// Creates and returns a new image that contains a snapshot of the currently playing video.
|
90 |
function sampleVideo(){ |
91 |
var newCanvas = document.createElement("canvas"); |
92 |
newCanvas.width = video.width; |
93 |
newCanvas.height = video.height; |
94 |
newCanvas.getContext("2d").drawImage( video, 0, 0, video.width, video.height ); |
95 |
return newCanvas; |
96 |
}
|
97 |
|
98 |
// renders the dark background for the whole canvas element. The background features a greyscale sample of the video and a gradient overlay
|
99 |
function drawBackground(){ |
100 |
var newCanvas = document.createElement("canvas"); |
101 |
var newContext = newCanvas.getContext("2d"); |
102 |
newCanvas.width = mainCanvas.width; |
103 |
newCanvas.height = mainCanvas.height; |
104 |
newContext.drawImage( video, 0, video.height * 0.1, video.width, video.height * 0.5, 0, 0, mainCanvas.width, mainCanvas.height ); |
105 |
|
106 |
var imageData, data; |
107 |
try{ |
108 |
imageData = newContext.getImageData( 0, 0, mainCanvas.width, mainCanvas.height ); |
109 |
data = imageData.data; |
110 |
} catch(error){ // CORS error (eg when viewed from a local file). Create a solid fill background instead |
111 |
newContext.fillStyle = "yellow"; |
112 |
newContext.fillRect( 0, 0, mainCanvas.width, mainCanvas.height ); |
113 |
imageData = mainContext.createImageData( mainCanvas.width, mainCanvas.height ); |
114 |
data = imageData.data; |
115 |
}
|
116 |
|
117 |
//loop through each pixel, turning its color into a shade of grey
|
118 |
for( var i = 0; i < data.length; i += 4 ){ |
119 |
var red = data[i]; |
120 |
var green = data[i + 1]; |
121 |
var blue = data[i + 2]; |
122 |
var grey = Math.max( red, green, blue ); |
123 |
|
124 |
data[i] = grey; |
125 |
data[i+1] = grey; |
126 |
data[i+2] = grey; |
127 |
}
|
128 |
newContext.putImageData( imageData, 0, 0 ); |
129 |
|
130 |
//add the gradient overlay
|
131 |
var gradient = newContext.createLinearGradient( mainCanvas.width/2, 0, mainCanvas.width/2, mainCanvas.height ); |
132 |
gradient.addColorStop( 0, '#000' ); |
133 |
gradient.addColorStop( 0.2, '#000' ); |
134 |
gradient.addColorStop( 1, "rgba(0,0,0,0.5)" ); |
135 |
newContext.fillStyle = gradient; |
136 |
newContext.fillRect( 0, 0, mainCanvas.width, mainCanvas.height ); |
137 |
|
138 |
mainContext.save(); |
139 |
mainContext.drawImage( newCanvas, 0, 0, mainCanvas.width, mainCanvas.height ); |
140 |
|
141 |
mainContext.restore(); |
142 |
}
|
143 |
|
144 |
// renders the 'film reel' animation that scrolls across the canvas
|
145 |
function drawFilm(){ |
146 |
var sampleWidth = 116; // the width of a sampled video frame, when painted on the canvas as part of a 'film reel' |
147 |
var sampleHeight = 80; // the height of a sampled video frame, when painted on the canvas as part of a 'film reel' |
148 |
var filmSpeed = 20; // determines how fast the 'film reel' scrolls across the generated canvas animation. |
149 |
var filmTop = 120; //the y co-ordinate of the 'film reel' animation |
150 |
var filmAngle = -10 * Math.PI / 180; //the slant of the 'film reel' |
151 |
var filmRight = ( videoSamples.length > 0 )? videoSamples[0].x + videoSamples.length * sampleWidth : mainCanvas.width; //the right edge of the 'film reel' in pixels, relative to the canvas |
152 |
|
153 |
//here, we check if the first frame of the 'film reel' has scrolled out of view
|
154 |
if( videoSamples.length > 0 ){ |
155 |
var bottomLeftX = videoSamples[0].x + sampleWidth; |
156 |
var bottomLeftY = filmTop + sampleHeight; |
157 |
bottomLeftX = Math.floor( Math.cos(filmAngle) * bottomLeftX - Math.sin(filmAngle) * bottomLeftY ); // the final display position after rotation |
158 |
|
159 |
if( bottomLeftX < 0 ){ //the frame is offscreen, remove it's refference from the film array |
160 |
videoSamples.shift(); |
161 |
}
|
162 |
}
|
163 |
|
164 |
// add new frames to the reel as required
|
165 |
while( filmRight <= mainCanvas.width ){ |
166 |
var newFrame = {}; |
167 |
newFrame.x = filmRight; |
168 |
newFrame.canvas = sampleVideo(); |
169 |
videoSamples.push(newFrame); |
170 |
filmRight += sampleWidth; |
171 |
}
|
172 |
|
173 |
// create the gradient fill for the reel
|
174 |
var gradient = mainContext.createLinearGradient( 0, 0, mainCanvas.width, mainCanvas.height ); |
175 |
gradient.addColorStop( 0, '#0D0D0D' ); |
176 |
gradient.addColorStop( 0.25, '#300A02' ); |
177 |
gradient.addColorStop( 0.5, '#AF5A00' ); |
178 |
gradient.addColorStop( 0.75, '#300A02' ); |
179 |
gradient.addColorStop( 1, '#0D0D0D' ); |
180 |
|
181 |
mainContext.save(); |
182 |
mainContext.globalAlpha = 0.9; |
183 |
mainContext.fillStyle = gradient; |
184 |
mainContext.rotate(filmAngle); |
185 |
|
186 |
// loops through all items of film array, using the stored co-ordinate values of each to draw part of the 'film reel'
|
187 |
for( var i in videoSamples ){ |
188 |
var sample = videoSamples[i]; |
189 |
var punchX, punchY, punchWidth = 4, punchHeight = 6, punchInterval = 11.5; |
190 |
|
191 |
//draws the main rectangular box of the sample
|
192 |
mainContext.beginPath(); |
193 |
mainContext.moveTo( sample.x, filmTop ); |
194 |
mainContext.lineTo( sample.x + sampleWidth, filmTop ); |
195 |
mainContext.lineTo( sample.x + sampleWidth, filmTop + sampleHeight ); |
196 |
mainContext.lineTo( sample.x, filmTop + sampleHeight ); |
197 |
mainContext.closePath(); |
198 |
|
199 |
//adds the small holes lining the top and bottom edges of the 'fim reel'
|
200 |
for( var j = 0; j < 10; j++ ){ |
201 |
punchX = sample.x + ( j * punchInterval ) + 5; |
202 |
punchY = filmTop + 4; |
203 |
mainContext.moveTo( punchX, punchY + punchHeight ); |
204 |
mainContext.lineTo( punchX + punchWidth, punchY + punchHeight ); |
205 |
mainContext.lineTo( punchX + punchWidth, punchY ); |
206 |
mainContext.lineTo( punchX, punchY ); |
207 |
mainContext.closePath(); |
208 |
punchX = sample.x + ( j * punchInterval ) + 5; |
209 |
punchY = filmTop + 70; |
210 |
mainContext.moveTo( punchX, punchY + punchHeight ); |
211 |
mainContext.lineTo( punchX + punchWidth, punchY + punchHeight ); |
212 |
mainContext.lineTo( punchX + punchWidth, punchY ); |
213 |
mainContext.lineTo( punchX, punchY ); |
214 |
mainContext.closePath(); |
215 |
}
|
216 |
mainContext.fill(); |
217 |
}
|
218 |
|
219 |
//loop through all items of videoSamples array, update the x co-ordinate values of each item, and draw its stored image onto the canvas
|
220 |
mainContext.globalCompositeOperation = 'lighter'; |
221 |
for( var i in videoSamples ){ |
222 |
var sample = videoSamples[i]; |
223 |
sample.x -= filmSpeed; |
224 |
mainContext.drawImage( sample.canvas, sample.x + 5, filmTop + 10, 110, 62 ); |
225 |
}
|
226 |
|
227 |
mainContext.restore(); |
228 |
}
|
229 |
|
230 |
// renders the canvas title
|
231 |
function drawTitle(){ |
232 |
mainContext.save(); |
233 |
mainContext.fillStyle = 'black'; |
234 |
mainContext.fillRect( 0, 0, 368, 25 ); |
235 |
mainContext.fillStyle = 'white'; |
236 |
mainContext.font = "bold 21px Georgia"; |
237 |
mainContext.fillText( "SINTEL", 10, 20 ); |
238 |
mainContext.restore(); |
239 |
}
|
240 |
|
241 |
// renders all the text appearing at the top left corner of the canvas
|
242 |
function drawDescription(){ |
243 |
var text = []; //stores all text items, to be displayed over time. the video is 60 seconds, and each will be visible for 10 seconds. |
244 |
text[0] = "Sintel is an independently produced short film, initiated by the Blender Foundation."; |
245 |
text[1] = "For over a year an international team of 3D animators and artists worked in the studio of the Amsterdam Blender Institute on the computer-animated short 'Sintel'."; |
246 |
text[2] = "It is an epic short film that takes place in a fantasy world, where a girl befriends a baby dragon."; |
247 |
text[3] = "After the little dragon is taken from her violently, she undertakes a long journey that leads her to a dramatic confrontation."; |
248 |
text[4] = "The script was inspired by a number of story suggestions by Martin Lodewijk around a Cinderella character (Cinder in Dutch is 'Sintel'). "; |
249 |
text[5] = "Screenwriter Esther Wouda then worked with director Colin Levy to create a script with multiple layers, with strong characterization and dramatic impact as central goals."; |
250 |
text = text[Math.floor( video.currentTime / 10 )]; //use the videos current time to determine which text item to display. |
251 |
|
252 |
mainContext.save(); |
253 |
var alpha = 1 - ( video.currentTime % 10 ) / 10; |
254 |
mainContext.globalAlpha = ( alpha < 5 )? alpha : 1; |
255 |
mainContext.fillStyle = '#fff'; |
256 |
mainContext.font = "normal 12px Georgia"; |
257 |
|
258 |
//break the text up into several lines as required, and write each line on the canvas
|
259 |
text = text.split(' '); |
260 |
var colWidth = mainCanvas.width * .75; |
261 |
var line = ''; |
262 |
var y = 40; |
263 |
for(var i in text ){ |
264 |
line += text[i] + ' '; |
265 |
if( mainContext.measureText(line).width > colWidth ){ |
266 |
mainContext.fillText( line, 10, y ); |
267 |
line = ''; |
268 |
y += 12; |
269 |
}
|
270 |
}
|
271 |
mainContext.fillText( line, 10, y ); |
272 |
|
273 |
mainContext.restore(); |
274 |
}
|
275 |
|
276 |
//updates the bottom-right potion of the canvas with the latest perfomance statistics
|
277 |
function drawStats( average ){ |
278 |
var x = 245.5, y = 130.5, graphScale = 0.25; |
279 |
|
280 |
mainContext.save(); |
281 |
mainContext.font = "normal 10px monospace"; |
282 |
mainContext.textAlign = 'left'; |
283 |
mainContext.textBaseLine = 'top'; |
284 |
mainContext.fillStyle = 'black'; |
285 |
mainContext.fillRect( x, y, 120, 75 ); |
286 |
|
287 |
//draw the x and y axis lines of the graph
|
288 |
y += 30; |
289 |
x += 10; |
290 |
mainContext.beginPath(); |
291 |
mainContext.strokeStyle = '#888'; |
292 |
mainContext.lineWidth = 1.5; |
293 |
mainContext.moveTo( x, y ); |
294 |
mainContext.lineTo( x + 100, y ); |
295 |
mainContext.stroke(); |
296 |
mainContext.moveTo( x, y ); |
297 |
mainContext.lineTo( x, y - 25 ); |
298 |
mainContext.stroke(); |
299 |
|
300 |
// draw the last 50 speedLog entries on the graph
|
301 |
mainContext.strokeStyle = '#00ffff'; |
302 |
mainContext.fillStyle = '#00ffff'; |
303 |
mainContext.lineWidth = 0.3; |
304 |
var imax = speedLog.length; |
305 |
var i = ( speedLog.length > 50 )? speedLog.length - 50 : 0 |
306 |
mainContext.beginPath(); |
307 |
for( var j = 0; i < imax; i++, j += 2 ){ |
308 |
mainContext.moveTo( x + j, y ); |
309 |
mainContext.lineTo( x + j, y - speedLog[i] * graphScale ); |
310 |
mainContext.stroke(); |
311 |
}
|
312 |
|
313 |
// the red line, marking the desired maximum rendering time
|
314 |
mainContext.beginPath(); |
315 |
mainContext.strokeStyle = '#FF0000'; |
316 |
mainContext.lineWidth = 1; |
317 |
var target = y - frameDuration * graphScale; |
318 |
mainContext.moveTo( x, target ); |
319 |
mainContext.lineTo( x + 100, target ); |
320 |
mainContext.stroke(); |
321 |
|
322 |
// current/average speedLog items
|
323 |
y += 12; |
324 |
if( average ){ |
325 |
var speed = 0; |
326 |
for( i in speedLog ){ speed += speedLog[i]; } |
327 |
speed = Math.floor( speed / speedLog.length * 10) / 10; |
328 |
}else { |
329 |
speed = speedLog[speedLog.length-1]; |
330 |
}
|
331 |
mainContext.fillText( 'Render Time: ' + speed, x, y ); |
332 |
|
333 |
// canvas fps
|
334 |
mainContext.fillStyle = '#00ff00'; |
335 |
y += 12; |
336 |
if( average ){ |
337 |
fps = 0; |
338 |
for( i in fpsLog ){ fps += fpsLog[i]; } |
339 |
fps = Math.floor( fps / fpsLog.length * 10) / 10; |
340 |
}else { |
341 |
fps = fpsLog[fpsLog.length-1]; |
342 |
}
|
343 |
mainContext.fillText( ' Canvas FPS: ' + fps, x, y ); |
344 |
|
345 |
// browser frames per second (fps), using window.mozPaintCount (firefox only)
|
346 |
if( window.mozPaintCount ){ |
347 |
y += 12; |
348 |
if( average ){ |
349 |
fps = 0; |
350 |
for( i in paintCountLog ){ fps += paintCountLog[i]; } |
351 |
fps = Math.floor( fps / paintCountLog.length * 10) / 10; |
352 |
}else { |
353 |
fps = paintCountLog[paintCountLog.length-1]; |
354 |
}
|
355 |
mainContext.fillText( 'Browser FPS: ' + fps, x, y ); |
356 |
}
|
357 |
|
358 |
mainContext.restore(); |
359 |
}
|
360 |
|
361 |
//draw the spining blade that appears in the begining of the animation
|
362 |
function drawBlade(){ |
363 |
if( !blade || blade.x > mainCanvas.width ){ return; } |
364 |
blade.x += 2.5; |
365 |
blade.angle = ( blade.angle - 45 ) % 360; |
366 |
|
367 |
//update blade, an ofscreen canvas containing the blade's image
|
368 |
var angle = blade.angle * Math.PI / 180; |
369 |
var bladeContext = blade.getContext('2d'); |
370 |
blade.width = blade.width; //clear the canvas |
371 |
bladeContext.save(); |
372 |
bladeContext.translate( 200, 200 ); |
373 |
bladeContext.rotate(angle); |
374 |
bladeContext.drawImage( bladeSrc, -bladeSrc.width/2, -bladeSrc.height/2 ); |
375 |
bladeContext.restore(); |
376 |
|
377 |
mainContext.save(); |
378 |
mainContext.globalAlpha = 0.95; |
379 |
mainContext.drawImage( blade, blade.x, blade.y + Math.sin(angle) * 50 ); |
380 |
mainContext.restore(); |
381 |
}
|
382 |
})();
|
Langkah 3: Optimalisasi Kode: Ketahui Aturannya
Aturan pertama dari optimasi kinerja kode adalah: Jangan.
Inti dari peraturan ini adalah untuk mencegah optimasi demi optimalisasi, karena prosesnya sesuai dengan harga tertentu.
Skrip yang sangat optimal akan memudahkan browser untuk mengurai dan mengolahnya, namun biasanya dengan beban bagi manusia yang akan merasa lebih sulit untuk diikuti dan dipelihara. Kapan pun Anda memutuskan bahwa beberapa pengoptimalan diperlukan, tetapkan beberapa tujuan sebelumnya sehingga Anda tidak terbawa oleh proses dan berlebihan.
Tujuan dalam mengoptimalkan widget ini adalah agar fungsi main()
berjalan dalam waktu kurang dari 33 milidetik sebagaimana mestinya, yang akan cocok dengan frame rate dari file video yang dimainkan (sintel.mp4
dan sintel.webm
). File-file ini dikodekan pada kecepatan pemutaran 30fps (tiga puluh frame per detik), yang berarti sekitar 0,33 detik atau 33 milidetik per frame ( 1 detik ÷ 30 frame ).
Karena JavaScript menggambar frame animasi baru ke canvas setiap kali fungsi main()
dipanggil, tujuan proses pengoptimalan kita adalah membuat fungsi ini memakan waktu 33 milidetik atau kurang setiap kali berjalan. Fungsi ini berulang kali memanggil dirinya sendiri menggunakan timer Javascript setTimeout()
seperti yang ditunjukkan di bawah ini.
1 |
|
2 |
var frameDuration = 33; // set the animation's target speed in milliseconds |
3 |
function main(){ |
4 |
if( video.paused || video.ended ){ return false; } |
5 |
setTimeout( main, frameDuration ); |
Aturan kedua: Belum.
Aturan ini menekankan bahwa pengoptimalan harus selalu dilakukan pada akhir proses pengembangan apabila Anda telah menyelesaikan beberapa kode yang bekerja dengan lengkap. Polisi optimalisasi akan membiarkan kita melakukan yang satu ini, karena skrip widget adalah contoh sempurna dari program yang bekerja dengan lengkap yang siap untuk prosesnya.
Aturan ketiga: Belum, dan profil dulu.
Aturan ini adalah tentang memahami program Anda dalam hal kinerja runtime. Profil membantu Anda mengetahui daripada menebak fungsi atau area skrip mana yang paling banyak digunakan atau paling sering digunakan, sehingga Anda dapat berfokus pada proses pengoptimalan. Hal ini cukup penting untuk membuat browser terkemuka dikirimkan dengan profiler JavaScript yang terpasang, atau memiliki ekstensi yang menyediakan layanan ini.
Saya menjalankan widget di bawah profiler di Firebug, dan di bawah adalah screenshot dari hasilnya.



Langkah 4: Tetapkan Beberapa Metrik Kinerja
Saat Anda menjalankan widget, saya yakin Anda menemukan semua barang Sintel baik-baik saja, dan benar-benar terpesona oleh item di sudut kanan bawah canvas, yang memiliki grafik dan teks yang indah.

Ini bukan hanya wajah cantik; kotak itu juga memberikan beberapa statistik kinerja real-time pada program yang sedang berjalan. Itu sebenarnya sederhana, profiler Javascript dasar. Benar! Yo, saya mendengar Anda menyukai profil, jadi saya memasukkan profiler ke dalam film Anda, sehingga Anda bisa mem-profil saat Anda menonton.
Grafiknya melacak Render Time, dihitung dengan mengukur berapa lama setiap menjalankan dari main()
yang dibutuhkan dalam milidetik. Karena ini adalah fungsi yang menarik setiap frame dari animasi, secara efektif frame rate dari animasi. Setiap garis biru vertikal pada grafik menggambarkan waktu yang dibutuhkan oleh satu frame. Garis horizontal merah adalah kecepatan target, yang kita atur pada 33ms agar sesuai dengan frame rate file video. Tepat di bawah grafik, kecepatan panggilan terakhir ke main()
yang diberikan dalam milidetik.
Profiler juga merupakan uji coba kecepatan rendering browser yang praktis. Saat ini, rata-rata waktu render di Firefox adalah 55ms, 90ms di IE 9, 41ms di Chrome, 148ms di Opera dan 63ms di Safari. Semua browser berjalan pada Windows XP, kecuali IE 9 yang diprofilkan pada Windows Vista.
Metrik berikutnya di bawah itu adalah Canvas FPS (frame per detik dari canvas), yang diperoleh dengan menghitung berapa kali main()
dipanggil per detik. Profiler menampilkan tingkat FPS Canvas terbaru saat video masih diputar, dan saat berakhir ia menunjukkan kecepatan rata-rata semua panggilan ke main()
.
Metrik terakhir adalah Browser FPS, yang mengukur berapa banyak browser menghapus jendela saat ini setiap detiknya. Yang satu ini hanya tersedia jika Anda melihat widget di Firefox, karena bergantung pada fitur yang saat ini hanya tersedia di browser itu yang disebut window.mozPaintCount.
, sebuah properti JavaScript yang melacak berapa kali jendela browser telah digambar ulang sejak halaman dimuat pertama kali.
Penggambaran ulang biasanya terjadi ketika sebuah peristiwa atau tindakan yang mengubah tampilan halaman, seperti saat Anda menggulir ke bawah halaman atau mouse-over sebuah link. Ini secara efektif merupakan frame rate nyata browser, yang ditentukan oleh seberapa sibuknya halaman web saat ini.
Untuk mengukur efek animasi canvas yang tidak dioptimalkan pada mozPaintCount
, saya menghapus tag canvas dan semua JavaScript, sehingga dapat melacak frame rate browser saat memutar video saja. Tes saya dilakukan di konsol Firebug, dengan menggunakan fungsi di bawah ini:
1 |
var lastPaintCount = window.mozPaintCount; |
2 |
setInterval( function(){ |
3 |
console.log( window.mozPaintCount - lastPaintCount ); |
4 |
lastPaintCount = window.mozPaintCount; |
5 |
}, 1000); |
Hasilnya: Frame rate browser adalah antara 30 dan 32 FPS saat video diputar, dan menurun ke FPS 0-1 saat video tersebut berakhir. Ini berarti bahwa Firefox menyesuaikan frekuensi penggambaran ulang jendela agar sesuai dengan video yang diputar, dienkodekan pada 30fps. Ketika tes dijalankan dengan animasi canvas yang tidak dioptimalkan dan video yang diputar bersamaan, ia akan melambat hingga 16fps, karena browser sekarang berjuang untuk menjalankan semua JavaScript dan masih menggambar ulang jendela tepat waktu, membuat pemutaran video dan animasi canvas melamban.
Kita sekarang akan mulai mengutak-atik program kita, dan saat kita melakukannya, kita akan terus melacak Render Time, Canvas FPS dan Browser FPS untuk mengukur dampak perubahan kita.
Langkah 5: Gunakan requestAnimationFrame()
Dua cuplikan JavaScript terakhir di atas menggunakan fungsi timer setTimeout()
dan setInterval()
. Untuk menggunakan fungsi ini, Anda menentukan interval waktu dalam milidetik dan fungsi callback yang Anda ingin dieksekusi setelah berlalunya waktu. Perbedaan antara keduanya adalah setTimeout()
akan memanggil fungsi Anda sekali saja, sementara setInterval()
memanggilnya berulang kali.
Sementara fungsi ini selalu menjadi alat yang sangat diperlukan dalam kit animator JavaScript, mereka memiliki beberapa kekurangan:
Pertama, set interval waktu tidak selalu bisa diandalkan. Jika program masih di tengah mengeksekusi sesuatu yang lain saat interval berlalu, fungsi callback akan dieksekusi nantinya dari yang semula ditetapkan, setelah browser tidak lagi sibuk. Pada fungsi main()
, kita menetapkan interval ke 33 milidetik - namun seiring profilernya mengungkapkan, fungsi ini sebenarnya dipanggil setiap 148 milidetik di Opera.
Kedua, ada masalah dengan penggambaran ulang browser. Jika kita memiliki fungsi callback yang menghasilkan 20 animasi frame per detik sementara browser menggambar ulang jendelanya hanya 12 kali per detik, 8 panggilan ke fungsi itu akan terbuang karena pengguna tidak akan pernah bisa melihat hasilnya.
Akhirnya, browser tidak memiliki cara untuk mengetahui bahwa fungsi yang dipanggil adalah elemen animasi dalam dokumen. Ini berarti bahwa jika elemen tersebut digeser keluar dari tampilan, atau pengguna mengklik tab lain, callback akan tetap dieksekusi berulang kali, menghabiskan siklus CPU.
Menggunakan requestAnimationFrame()
memecahkan sebagian besar masalah ini, dan ini bisa digunakan sebagai pengganti fungsi timer dalam animasi HTML5. Alih-alih menentukan interval waktu, requestAnimationFrame()
menyinkronkan fungsi panggilan dengan penggambaran ulang jendela browser. Ini menghasilkan animasi yang lebih cair dan konsisten karena tidak ada frame yang dijatuhkan, dan browser dapat melakukan pengoptimalan internal lebih jauh karena mengetahui animasi sedang berlangsung.
Untuk mengganti setTimeout()
dengan requestAnimationFrame
di widget kita, pertama-tama tambahkan baris berikut di bagian atas skrip kita:
1 |
requestAnimationFrame = window.requestAnimationFrame || |
2 |
window.mozRequestAnimationFrame || |
3 |
window.webkitRequestAnimationFrame || |
4 |
window.msRequestAnimationFrame || |
5 |
setTimeout; |
Karena spesifikasinya masih cukup baru, beberapa browser atau versi browser memiliki implementasi eksperimental mereka sendiri, baris ini memastikan bahwa nama fungsi menunjuk ke metode yang benar jika tersedia, dan berbalik kembali ke setTimeout()
jika tidak. Kemudian pada fungsi main()
, kita mengubah baris ini:
1 |
|
2 |
setTimeout( main, frameDuration ); |
...menjadi:
1 |
requestAnimationFrame( main, canvas ); |
Parameter pertama mengambil fungsi callback, yang dalam hal ini adalah fungsi main()
. Parameter kedua adalah opsional, dan menentukan elemen DOM yang berisi animasi. Ini seharusnya digunakan untuk menghitung pengoptimalan tambahan.
Perhatikan bahwa fungsi getStats()
juga menggunakan setTimeout()
, namun kita membiarkannya karena fungsi khusus ini tidak ada hubungannya dengan menganimasikan adegan. requestAnimationFrame()
dibuat khusus untuk animasi, jadi jika fungsi callback Anda tidak melakukan animasi, Anda masih bisa menggunakan setTimeout()
atau setInterval()
.
Langkah 6: Gunakan API Page Visibility
Pada langkah terakhir kita membuat requestAnimationFrame
menggerakkan animasi canvas, dan sekarang kita memiliki masalah baru. Jika kita mulai menjalankan widget, kemudian minimalkan jendela browser atau beralih ke tab baru, tingkat jendela widget menggambar ulang menurun untuk menghemat daya. Ini juga memperlambat animasi canvas karena sekarang disinkronisasi dengan tingkat menggambar ulang - yang akan sempurna jika video tidak terus diputar sampai akhir.
Kita membutuhkan cara untuk mendeteksi kapan halaman tidak dilihat sehingga kita bisa menghentikan sementara pemutaran video; di sinilah API Page Visibility datang untuk menyelamatkan.
API berisi seperangkat properti, fungsi dan event yang dapat kita gunakan untuk mendeteksi apakah ada halaman web yang dilihat atau disembunyikan. Kita kemudian dapat menambahkan kode yang menyesuaikan perilaku program kita. Kita akan menggunakan API ini untuk mem-pause pemutaran video pada widget setiap kali halaman tidak aktif.
Kita mulai dengan menambahkan event listener baru ke skrip kita:
1 |
|
2 |
document.addEventListener( 'visibilitychange', onVisibilityChange, false); |
Selanjutnya datanglah fungsi event handler:
1 |
|
2 |
// Adjusts the program behavior, based on whether the webpage is active or hidden
|
3 |
function onVisibilityChange() { |
4 |
if( document.hidden && !video.paused ){ |
5 |
video.pause(); |
6 |
}else if( video.paused ){ |
7 |
video.play(); |
8 |
}
|
9 |
}
|
Langkah 7: Untuk Shape Kustom, Gambarkan Seluruh Path Sekaligus
Path digunakan untuk membuat dan menggambar shape dan garis tepi pada elemen <canvas>
, yang setiap saat akan memiliki satu path aktif.
Path berisi daftar sub-path, dan setiap sub-path terdiri dari titik koordinat canvas yang dihubungkan bersama oleh garis atau kurva. Semua fungsi pembuatan dan penggambaran path adalah properti dari objek context
canvas, dan dapat digolongkan menjadi dua kelompok.
Ada fungsi pembuatan subpath, yang digunakan untuk menentukan subpath dan menyertakan lineTo()
, quadraticCurveTo()
, bezierCurveTo()
, dan arc()
. Kemudian kita memiliki stroke()
dan fill()
, fungsi menggambar path/subpath. Menggunakan stroke()
akan menghasilkan garis tepi, sedangkan fill()
menghasilkan shape yang diisi dengan warna, gradien atau pola.
Saat menggambar shape dan garis tepi pada canvas, lebih efisien untuk membuat keseluruhan path terlebih dulu, lalu hanya stroke()
atau fill()
padanya sekali, daripada menentukan dan menggambar setiap supbath sekaligus. Dengan mengambil grafik profiler yang dijelaskan pada Langkah 4 sebagai contoh, masing-masing garis vertikal biru adalah subpath, sementara semuanya bersama-sama membentuk path saat ini.

Metode stroke()
saat ini dipanggil dalam satu loop yang mendefinisikan setiap subpath:
1 |
|
2 |
mainContext.beginPath(); |
3 |
for( var j = 0; i < imax; i++, j += 2 ){ |
4 |
mainContext.moveTo( x + j, y ); // define the subpaths starting point |
5 |
mainContext.lineTo( x + j, y - speedLog[i] * graphScale ); // set the subpath as a line, and define its endpoint |
6 |
mainContext.stroke(); // draw the subpath to the canvas |
7 |
}
|
Grafik ini dapat ditarik jauh lebih efisien dengan terlebih dahulu mendefinisikan semua subpath, lalu menggambar keseluruhan path saat ini sekaligus, seperti yang ditunjukkan di bawah ini.
1 |
|
2 |
mainContext.beginPath(); |
3 |
for( var j = 0; i < imax; i++, j += 2 ){ |
4 |
mainContext.moveTo( x + j, y ); // define the subpaths starting point |
5 |
mainContext.lineTo( x + j, y - speedLog[i] * graphScale ); // set the subpath as a line, and define its endpoint |
6 |
}
|
7 |
mainContext.stroke(); // draw the whole current path to the mainCanvas. |
Langkah 8: Gunakan Canvas Off-Screen untuk Membangun Adegan
Teknik pengoptimalan ini terkait dengan yang ada di langkah sebelumnya, bahwa keduanya didasarkan pada prinsip yang sama untuk meminimalkan penggambaran ulang halaman web.
Setiap kali terjadi sesuatu yang mengubah tampilan atau konten dokumen, browser harus menjadwalkan operasi penggambaran ulang segera setelahnya untuk memperbarui antarmukanya. Penggambaran ulang bisa menjadi operasi yang mahal dalam hal siklus dan kekuatan CPU, terutama untuk halaman yang padat dengan banyak elemen dan animasi yang sedang berlangsung. Jika Anda membangun adegan animasi yang kompleks dengan menambahkan banyak item satu per satu ke <canvas>
, setiap penambahan baru mungkin akan memicu keseluruhan penggambaran ulang.
Lebih baik dan lebih cepat untuk membangun adegan di luar layar (dalam memori) <canvas>
, dan sekali selesai, gambar seluruh adegan hanya sekali ke layar, <canvas>
yang terlihat.
Tepat di bawah kode yang mendapatkan rujukan ke widget <canvas>
dan konteksnya, kami akan menambahkan lima baris baru yang membuat objek DOM canvas di luar layar dan sesuai dengan dimensi aslinya, <canvas>
yang terlihat.
1 |
|
2 |
var mainCanvas = document.getElementById("mainCanvas"); // points to the on-screen, original HTML canvas element |
3 |
var mainContext = mainCanvas.getContext('2d'); // the drawing context of the on-screen canvas element |
4 |
var osCanvas = document.createElement("canvas"); // creates a new off-screen canvas element |
5 |
var osContext = osCanvas.getContext('2d'); //the drawing context of the off-screen canvas element |
6 |
osCanvas.width = mainCanvas.width; // match the off-screen canvas dimensions with that of #mainCanvas |
7 |
osCanvas.height = mainCanvas.height; |
Kita kemudian akan melakukan pencarian dan penggantian di semua fungsi menggambar untuk semua referensi ke "mainCanvas" dan mengubahnya menjadi "osCanvas". Referensi ke "mainContext" akan diganti dengan "osContext". Segalanya sekarang akan tertarik ke canvas di luar layar yang baru, bukannya <canvas>
yang asli.
Akhirnya, kita menambahkan satu baris lagi ke main()
yang menggambar apa yang saat ini ada di luar layar <canvas>
ke dalam <canvas>
asli kita.
1 |
|
2 |
// As the scripts main function, it controls the pace of the animation
|
3 |
function main(){ |
4 |
requestAnimationFrame( main, mainCanvas ); |
5 |
if( video.paused || video.currentTime > 59 ){ return; } |
6 |
|
7 |
var now = new Date().getTime(); |
8 |
if( frameStartTime ){ |
9 |
speedLog.push( now - frameStartTime ); |
10 |
}
|
11 |
frameStartTime = now; |
12 |
if( video.readyState < 2 ){ return; } |
13 |
|
14 |
frameCount++; |
15 |
osCanvas.width = osCanvas.width; //clear the offscreen canvas |
16 |
drawBackground(); |
17 |
drawFilm(); |
18 |
drawDescription(); |
19 |
drawStats(); |
20 |
drawBlade(); |
21 |
drawTitle(); |
22 |
mainContext.drawImage( osCanvas, 0, 0 ); // copy the off-screen canvas graphics to the on-screen canvas |
23 |
}
|
Langkah 9: Cache Path Sebagai Gambar Bitmap Bila Memungkinkan
Untuk banyak jenis grafis, menggunakan drawImage()
akan jauh lebih cepat daripada membuat gambar yang sama pada kanvas menggunakan path. Jika Anda menemukan bahwa ramuan besar skrip Anda dihabiskan berulang kali menggambar shape dan garis tepi yang sama berulang-ulang, Anda dapat menghemat beberapa pekerjaan browser dengan menyimpan gambar yang dihasilkan sebagai gambar bitmap, lalu melukisnya sekali saja ke canvas kapan pun dibutuhkan dengan menggunakan drawImage()
.
Ada dua cara untuk melakukan ini.
Yang pertama adalah dengan membuat file gambar eksternal sebagai gambar JPG, GIF atau PNG, lalu memuatnya secara dinamis menggunakan JavaScript dan menyalinnya ke canvas Anda. Satu kelemahan dari metode ini adalah file tambahan yang harus diunduh program Anda dari jaringan, namun tergantung pada jenis grafis atau aplikasi Anda, ini sebenarnya bisa menjadi solusi yang baik. Widget animasi menggunakan metode ini untuk memuat grafik pisau berputar, yang tidak mungkin dibuat ulang hanya dengan menggunakan fungsi menggambar path dari canvas.



Metode kedua melibatkan hanya dengan menggambar grafis sekali ke canvas di luar layar daripada memuat gambar eksternal. Kita akan menggunakan metode ini untuk men-cache judul dari widget animasi. Kita pertama kali membuat variabel untuk referensi elemen canvas di luar layar baru yang akan dibuat. Nilai defaultnya diset menjadi false
, sehingga kita dapat mengetahui apakah cache gambar telah dibuat dan disimpan begitu skrip mulai berjalan:
1 |
|
2 |
var titleCache = false; // points to an off-screen canvas used to cache the animation scene's title |
Kita kemudian mengedit fungsi drawTitle()
untuk pertama-tama memeriksa apakah gambar canvas titleCache
telah dibuat. Jika belum, itu akan menciptakan gambar di luar layar dan menyimpan referensi ke titleCache
:
1 |
|
2 |
// renders the canvas title
|
3 |
function drawTitle(){ |
4 |
if( titleCache == false ){ // create and save the title image |
5 |
titleCache = document.createElement('canvas'); |
6 |
titleCache.width = osCanvas.width; |
7 |
titleCache.height = 25; |
8 |
|
9 |
var context = titleCache.getContext('2d'); |
10 |
context.fillStyle = 'black'; |
11 |
context.fillRect( 0, 0, 368, 25 ); |
12 |
context.fillStyle = 'white'; |
13 |
context.font = "bold 21px Georgia"; |
14 |
context.fillText( "SINTEL", 10, 20 ); |
15 |
}
|
16 |
|
17 |
osContext.drawImage( titleCache, 0, 0 ); |
18 |
}
|
Langkah 10: Kosongkan Canvas dengan clearRect()
Langkah pertama dalam menggambar frame animasi baru adalah dengan membersihkan canvas saat ini. Ini dapat dilakukan dengan mengatur ulang lebar elemen canvas, atau menggunakan fungsi clearRect()
.
Mengatur ulang lebar memiliki efek samping juga membersihkan context canvas saat ini kembali ke keadaan standarnya, yang dapat memperlambat segalanya. Menggunakan clearRect()
selalu merupakan cara yang lebih cepat dan lebih baik untuk membersihkan canvas.
Pada fungsi main()
, kita akan mengubah ini:
1 |
|
2 |
osCanvas.width = osCanvas.width; //clear the off-screen canvas |
...menjadi ini:
1 |
|
2 |
osContext.clearRect( 0, 0, osCanvas.width, osCanvas.height ); //clear the offscreen canvas |
Langkah 11: Menerapkan Lapisan
Jika Anda pernah bekerja dengan perangkat lunak pengedit gambar atau video seperti Gimp atau Photoshop sebelumnya, maka Anda sudah terbiasa dengan konsep dari lapisan, di mana gambar disusun dengan menumpuk banyak gambar di atas satu sama lain, dan masing-masing dapat dipilih dan diedit secara terpisah.
Diterapkan pada adegan animasi canvas, setiap lapisan akan menjadi elemen canvas terpisah, ditempatkan di atas satu sama lain menggunakan CSS untuk menciptakan ilusi satu elemen. Sebagai teknik pengoptimalan, ia bekerja paling baik bila ada perbedaan yang jelas antara elemen-elemen latar depan dan latar belakang adegan, dengan sebagian besar tindakan berlangsung di latar depan. Latar belakang kemudian dapat ditarik pada elemen canvas yang tidak banyak berubah antara frame animasi, dan latar depan pada elemen canvas yang lebih dinamis di atasnya. Dengan cara ini, keseluruhan adegan tidak harus digambar ulang lagi untuk setiap frame animasi.
Sayangnya, widget animasi adalah contoh bagus dari sebuah adegan dimana kita tidak dapat menggunakan teknik ini dengan baik, karena elemen latar depan dan latar belakang sangat dianimasikan.



Langkah 12: Perbaharui Hanya Area yang Berubah dari Adegan Animasi
Ini adalah teknik optimasi lain yang sangat bergantung pada komposisi adegan animasi. Ini bisa digunakan saat adegan animasi terkonsentrasi di sekitar area persegi tertentu di canvas. Kita kemudian bisa jelas dan menggambar ulang hanya pada daerah itu.
Misalnya, judul Sintel tetap tidak berubah sepanjang sebagian besar animasi, jadi kita bisa meninggalkan area itu tetap utuh saat membersihkan canvas untuk frame animasi berikutnya.



Untuk menerapkan teknik ini, kita mengganti baris yang memanggil fungsi menggambar judul di main()
dengan blok berikut:
1 |
|
2 |
if( titleCache == false ){ // If titleCache is false, the animation's title hasn't been drawn yet |
3 |
drawTitle(); // we draw the title. This function will now be called just once, when the program starts |
4 |
osContext.rect( 0, 25, osCanvas.width, osCanvas.height ); // this creates a path covering the area outside by the title |
5 |
osContext.clip(); // we use the path to create a clipping region, that ignores the title's region |
6 |
}
|
Langkah 13: Minimalkan Rendering Sub-Pixel
Rendering sub-pixel atau anti-aliasing terjadi saat browser secara otomatis menerapkan efek grafis untuk menghilangkan tepi yang bergerigi. Ini menghasilkan gambar dan animasi yang tampak lebih halus, dan otomatis diaktifkan setiap kali Anda menentukan koordinat fraksional daripada jumlah keseluruhan saat menggambar ke canvas.
Saat ini tidak ada standar tentang bagaimana hal itu harus dilakukan, jadi rendering subpiksel agak tidak konsisten di seluruh browser dalam hal output yang di-render. Ini juga memperlambat kecepatan me-render karena browser harus melakukan beberapa perhitungan untuk menghasilkan efeknya. Karena anti-aliasing canvas tidak bisa langsung dimatikan, satu-satunya cara untuk mengatasinya adalah dengan selalu menggunakan bilangan bulat dalam koordinat gambar Anda.
Kita akan menggunakan Math.floor()
untuk memastikan bilangan dalam skrip kita bila diperlukan. Sebagai contoh, baris berikut di drawFilm()
:
1 |
|
2 |
punchX = sample.x + ( j * punchInterval ) + 5; // the x co-ordinate |
...ditulis ulang sebagai:
1 |
|
2 |
punchX = sample.x + ( j * punchInterval ) + 5; // the x co-ordinate |
Langkah 14: Mengukur Hasilnya
Kita telah melihat beberapa teknik pengoptimalan animasi canvas, dan sekarang saatnya untuk meninjau hasilnya.



Tabel ini menunjukkan rata-rata Render Times sebelum dan sesudah dan Canvas FPS..API Canvas masih cukup baru dan berkembang setiap hari, jadi teruslah bereksperimen, menguji, mengeksplorasi dan berbagi. Terima kasih telah membaca tutorial. Kita dapat melihat beberapa peningkatan yang signifikan di semua browser, meskipun Chrome hanya yang mendekati pencapaian tujuan awal Render Time maksimum 33ms. Ini berarti masih banyak pekerjaan yang harus dilakukan untuk mendapatkan target tersebut.
Kita bisa melanjutkan dengan menerapkan teknik optimasi JavaScript yang lebih umum, dan jika itu masih gagal, mungkin pertimbangkan untuk menurunkan animasinya dengan menghilangkan beberapa peringatan. Tapi kita tidak akan melihat teknik lain hari ini, karena fokusnya adalah optimasi dari animasi <canvas>
.
API Canvas masih cukup baru dan berkembang setiap hari, jadi teruslah bereksperimen, menguji, mengeksplorasi dan berbagi. Terima kasih telah membaca tutorial.