Advertisement
  1. Web Design
  2. HTML/CSS
  3. Animation

HTML5 Полотно оптимізація: практичний приклад

Scroll to top
Read Time: 31 min

() translation by (you can also view the original English article)

Якщо ви займаєтеся розробкою на JavaScript досить довго, ви, швидше за все, розбився Ваш браузер кілька разів. Проблема, як правило, виявляється деякі JavaScript помилка, як нескінченний цикл while; якщо ні, то наступний підозрюваний-сторінка перетворень або анімації, що включає додавання і видалення елементів на веб-сторінці або анімації CSS властивості.  Цей підручник фокусується на оптимізацію анімації виробляється з допомогою JS і HTML5 в елемент .

 Цей підручник починається і закінчується з тим, що віджет HTML5 анімації, які ви бачите нижче:

 Ми візьмемо його з нами в подорож, досліджуючи різні виникають полотно поради з оптимізації та методи їх застосування у вихідному коді JavaScript віджета. Мета полягає в тому, щоб поліпшити швидкість виконання віджета і в кінцевому підсумку з гладкою, більше рідини віджет анімація, живлення від компактною, більш ефективний код JavaScript.

 Вихідний файл містить HTML і JavaScript від кожного кроку в підручник, так що ви можете слідувати поряд з будь-якої точки.

 Давайте зробимо перший крок.


Крок 1: грати в кіно трейлер 

 Віджет створений на основі фільму Трейлер Синтел, 3D анімаційне кіно Фонд блендер. Він побудований з використанням двох з найбільш цікавих доповнень в HTML5: на елементі і елементи.

 В теге для завантаження і відтворення відео файлів Синтел, в той час як <полотно> генерує свою послідовність анімації зйомки ігрового відео і змішування його з тексту та іншої графіки. При натисканні на відтворення відео, полотно оживає з темним фоном, що велика чорно-біла копія ігрового відео.  Менше, кольоровий екран-кадри відео копіюються на місце, і вона промайне, як частина плівки малюнку.

 У лівому верхньому куті, заголовок і кілька рядків тексту, які з'являються і зникають, як відтворюється анімація.  Швидкість роботи скрипта і супутні показники включені як частина анімації, невеликий чорний ящик в лівому нижньому куті графіка і яскравий текст. Ми будемо дивитися на цей конкретний пункт більш докладно пізніше.

 Нарешті, є великий обертається лопаті, яка літає по сцені на початку анімації, графіки яких завантажується з зовнішнього файлу зображення у форматі PNG.


Крок 2: перегляньте вихідний код 

 Вихідний код містить звичайне з'єднання з HTML, CSS і JavaScript. HTML є негусто: тільки з допомогою і теги, укладений в контейнер з:

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>

 Контейнер присвоюється ідентифікатор (animationWidget), яка виступає в якості гака для всіх правила CSS застосовуються до нього і його зміст (нижче).

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
}

 У той час як HTML і CSS є мариновані спеції і приправи, його в JavaScript, що це м'ясо віджета.

  •  У верхній частині, ми маємо основні об'єкти, які будуть часто використовуватися через скрипт, у тому числі посилання на елемент canvas, а її 2D-контекст.
  •  Функція init() викликається щоразу, коли відео починає грати, і встановлює всі об'єкти, що використовуються в сценарії.
  •  У sampleVideo (функція) відображає поточного кадру відтвореного відео, а setBlade() завантажує зовнішні зображення вимагає анімації.
  •  Темпи і зміст анімації контролюються функції main (), яка є як серцебиття сценарію. На регулярній основі, як тільки відео починає грати, він малює кожен кадр анімації, знявши спочатку полотно, то виклик кожного з п'яти функцій скрипта малювання:
    • метод drawbackground() 
    • drawFilm() 
    • drawTitle() 
    • drawDescription() 
    • drawStats() 

 Як видно з назв, кожна функція малювання відповідає за малювання елемента в сцені анімації. Структурувати код, таким чином, підвищує гнучкість і дозволяє в подальшому легше.

 Повний сценарій показана нижче. Знайдіть хвилинку, щоб оцінити його, і подивитися, якщо ви можете визначити будь-які зміни, які ви хотіли зробити, щоб його прискорити.

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
})();

Крок 3: оптимізація коду: знати правила 

Перше правило кодексу оптимізація продуктивності: немає. 

 Зміст цього правила полягає в запобіганні оптимізація заради оптимізації, так як процес відбувається за ціною.

 Оптимізований скрипт буде легше для браузера, щоб проаналізувати і обробити, але, як правило, з тягарем для людей, які буде важче стежити і підтримувати. Всякий раз, коли ви вирішите, що деякі оптимізації необхідно, ставити якісь цілі заздалегідь, так що ви не захоплюйтеся процесом і перестаратися.

 Мета оптимізації цей віджет буде мати функції main() виконати менш ніж за 33 мілісекунди, як це передбачається, який буде відповідати частота кадрів відтворення відео файлів (синтэл.MP4 і Синтел.формат WebM). Ці файли були закодовані зі швидкістю 30 кадрів в секунду (тридцяти кадрів в секунду), що означає приблизно 0,33 сек або 33 мілісекунд на Кадр ( 1 секунда ÷ 30 кадрів ).

 Оскільки JavaScript малює новий кадр анімації на полотно кожен раз, коли функція main() викликається, мета нашого процесу оптимізації буде зробити цю функцію беруть 33 мілісекунд або менше кожен раз, коли він працює. Ця функція багаторазово викликає сама себе за допомогою методу setTimeout() в JavaScript таймер, як показано нижче.

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 );

Друге правило: поки немає. 

 Це правило підкреслює, що оптимізація завжди повинно бути зроблено наприкінці процесу розвитку, коли ви вже конкретизовані деякі повний робочий код. Поліція оптимізація дозволить нам йти на це, оскільки скрипт віджету-прекрасний приклад повного, робоча програма, готова до процесу.

Третє правило: не ще, а профіль першого. 

 Це правило про розуміння вашої програми з точки зору продуктивності. Профілювання допоможе вам знати, а не здогадуватися, які функції або галузі сценарію займають більшу частину часу або часто використовуються, так що ви можете зосередитися на тих, в процесі оптимізації. Досить критично важлива для провідних браузерів корабель з вбудований в JavaScript профилометры, або розширення, які надають цю послугу. 

 Я побіг віджет під профайлером в Firebug, і нижче представлений скріншот результатів.


Крок 4: Встановіть Деякі Показники Продуктивності 

 Як ви запустили віджет, я впевнений, що ви знайшли всі Синтел речі в порядок, і були абсолютно вражені елемент у правому нижньому кутку полотна, з красивою графічної і блискучий текст.

 Це не просто гарне обличчя, що також забезпечує деяку статистику продуктивності в реальному часі на запущеної програми. Його насправді простий, голі кістки профилировщика JavaScript.  Це вірно! Я чув, що ти, як профілювання, так що я поклав профілювальник в кіно, так що ви можете профіль під час перегляду.

 Цей графік відстежує час рендеринга, розраховується шляхом вимірювання, як довго кожен запуск функції main() у мілісекундах.  Оскільки це функція, яка розмальовує кожен кадр анімації, це ефективно частоту кадрів анімації. Кожна вертикальна синя лінія на діаграмі показано час, що витрачається на один кадр.  Червона горизонтальна лінія-це швидкість цілі, які ми ставимо на 33ms, щоб відповідати частоту кадрів відео файлів. Трохи нижче графік, швидкість останнього дзвінка main() дається в мілісекундах.

 Профайлер-це також зручний браузер візуалізації тест швидкості. На даний момент, середній час рендеринга в Firefox 55ms, 90МС в IE 9, 41ms в Chrome, 148ms в опері і 63ms в Safari.  Всі браузери були запущені на Windows XP, за винятком IE 9, який був представлений в Windows Vista.

 Наступний метричний нижче, що це полотно кадрів в секунду (полотно кадрів в секунду), отриманих шляхом підрахунку, скільки разів в Main() викликається в секунду. Профайлер відображає останній полотно частоту кадрів коли відео грає досі, та коли вона закінчується, він показує середню швидкість всіх викликів функції main().

 Остання метрика браузера ФПС, який вимірює, скільки браузер перемальовує поточне вікно кожну секунду. Це доступно тільки при перегляді віджетів в Firefox, так як він залежить від того, в даний час функція доступна тільки в браузері під назвою Вікно.mozPaintCount., властивість JavaScript, який відстежує, скільки разів у вікні браузера була перефарбована так як веб-сторінка спочатку завантажується.

 На перефарбовує, як правило, відбуваються, коли подія або дія, що змінює вигляд сторінки відбувається, наприклад, коли ви прокрутіть сторінку вниз або наведіть вказівник миші на посилання. Це фактично реальна частота кадрів в браузері, який визначається як зайнятий поточної веб-сторінки.

 Щоб оцінити, який ефект неоптимізованих анімації на mozPaintCount, я прибрав тег Canvas і JavaScript, так як для відстеження частоти кадрів браузері при програванні тільки відео. Мої тести були зроблені в консолі Firebug, за допомогою функції нижче:

1
	var lastPaintCount = window.mozPaintCount;
2
	setInterval( function(){
3
		console.log( window.mozPaintCount - lastPaintCount );
4
		lastPaintCount = window.mozPaintCount;
5
	}, 1000);

 Результати: частота кадрів браузер був між 30 і 32 ФПС, коли відео грає, і впала до 0-1 ФПС, коли відео закінчилося. Це означає, що Firefox був регулювати частоту вікно перемальовується відповідно ігрове відео, закодоване при 30 кадрів в секунду.  Коли був виконаний тест з ООН-оптимізований анімації і відео грають разом, він сповільнився до 16fps, як браузер був зараз з усіх сил, щоб виконати всі JavaScript і ще перефарбувати її вікно на час, що відтворення відео і анімації полотно мляво.

 Ми зараз почати налаштування нашої програми, і як ми це зробимо, ми будемо відстежувати час рендеринга, полотно ФПС і ФПС браузера, щоб оцінити ефект зміни.


Крок 5: Використовуйте метод requestAnimationFrame() 

 Останні два вищенаведених фрагментах коду JavaScript використовувати функції setTimeout() і setInterval() функції таймера. Щоб використовувати ці функції, можна вказати інтервал часу в мілісекундах і функції зворотного виклику, ви хочете виконати закінчення цього часу.  Різниця між двома полягає в тому, що метод setTimeout() буде викликати функцію тільки один раз, а через setInterval() викликає його повторно.

 Хоча ці функції завжди були незамінними інструментами в комплекті на JavaScript аніматора, у них є кілька недоліків:

 По-перше, часовий інтервал встановити не завжди надійні. Якщо програма все ще знаходиться в середині виконання чого-то ще, коли інтервал закінчується, функція зворотного виклику буде виконуватися пізніше, ніж початково встановлений, як тільки браузер більше не зайнятий.  У функції main (), ми встановили інтервал 33 мс - але як профайлер показує, що функція насправді називається Все 148 мілісекунд в опері.

 По-друге, існує проблема з перефарбовує браузера. Якщо б у нас була функція зворотного виклику, яка генерується 20 кадрів анімації в секунду, в той час як браузер перефарбували свого вікна лише 12 разів на секунду, 8 виклики цієї функції будуть витрачені даремно, так як користувач ніколи не отримаєте, щоб побачити результати.

 Нарешті, браузер не має можливості дізнатися, що викликається функція анімує елементів в документі. Це означає, що якщо ці елементи прокрутки поза увагою, або користувач натискає на іншу вкладку, зворотний виклик, як і раніше, вам неодноразово виконуватися, витрачати цикли процесора.

 Використовуючи метод requestAnimationFrame() вирішує більшість цих проблем, і її можна використовувати замість функції таймера анімації в HTML5. Замість вказівки тимчасового інтервалу, метод requestAnimationFrame() синхронізує викликів функцій у вікні браузера перефарбовує.  Це призводить до збільшення об'єму рідини, послідовне анімація як немає кадрів, і браузер може зробити подальше внутрішньої оптимізації знаючи анімація в прогрес.

 Щоб замінити функції setTimeout() з допомогою методу requestAnimationFrame в наш віджет, то спочатку додайте такий рядок у верхній частині нашого скрипта:

1
requestAnimationFrame = window.requestAnimationFrame || 
2
						window.mozRequestAnimationFrame || 
3
						window.webkitRequestAnimationFrame || 
4
						window.msRequestAnimationFrame || 
5
						setTimeout;

 Як специфікація є ще досить новою, в деяких браузерах або в різних версіях однієї браузера мають власних експериментальних реалізацій, ця лінія гарантує, що ім'я функції вказує на правильний метод, якщо він доступний, і падає назад в setTimeout (), якщо не.  Тоді у функції main (), ми змінюємо цю рядок:

1
 
2
	setTimeout( main, frameDuration );

...щоб: 

1
	requestAnimationFrame( main, canvas );

 Перший параметр приймає функцію зворотного виклику, яка в даному випадку є функція main (). Другий параметр є необов'язковим і вказує на елемент DOM, який містить анімацію. Вона повинна бути використана для розрахунку додаткових оптимізацій.

 Зверніть увагу, що getStats() функція також використовує метод setTimeout(), але ми залишимо це в місці, оскільки ця функція не має нічого спільного з анімацією сцени. метод requestAnimationFrame() був створений спеціально для анімації, так що якщо ваша функція зворотного виклику не роблю анімації, ви можете використовувати setTimeout() або setInterval().


Крок 6: Використовуйте сторінку API для видимості 

 В останньому кроці ми зробили метод requestAnimationFrame потужність полотно анімації, і тепер у нас нова проблема. Якщо у нас почати працювати віджет, а потім згорнути вікно браузера або перейти на нову вкладку, Вікно якої віджета перефарбувати дроселі вниз, щоб зберегти владу.  Це також уповільнює полотно анімації, оскільки він тепер синхронізований з темпом перефарбувати - що було б чудово, якщо відео не продовжувати грати до кінця.

 Нам потрібен спосіб, щоб визначити, коли сторінка не проглядається, так що ми можете призупинити відтворення відео; це коли сторінка API видимості приходить на допомогу.

 API містить набір властивостей, функцій і подій ми можемо використовувати, щоб виявити, якщо веб-сторінка має вигляд або прихована.  Потім ми можемо додати код, який регулює поведінку нашої програми відповідно. Ми будемо використовувати цей API, щоб призупинити відтворення відео віджета всякий раз, коли сторінка неактивна.

Ми почнемо з додавання нового прослушивателя подій для нашого скрипта: 

1
 
2
	document.addEventListener( 'visibilitychange', onVisibilityChange, false);

Далі йде функція обробника подій: 

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
}

Крок 7: для нестандартної фігури, намалювати весь шлях відразу 

 Доріжки використовуються, щоб створити і намалювати власні форми й обриси на елементі елемент, який буде в усі часи мати один активний шлях.

 Шлях містить список суб-шляху, і кожен суб-шлях складається з полотна координатою точки пов'язані або лінією, або кривої. Весь шлях створення і малювання функцій властивості об'єкта контексту полотна, і можуть бути класифіковані на дві групи.

 Є подпути-функції, які використовуються для визначення подпути і включають кінці lineto(), quadraticCurveTo(), bezierCurveTo () і дугового().  Тоді у нас є інсульт() і заповнити(), шлях/подпуть функції малювання. Використовуючи інсульт() створює контур, а заливку() створює заповнювати форму чи колір, градієнт або візерунок.

 При малюванні фігури і начерки на полотні, ефективніше спочатку створити весь шлях, тоді точно інсульт() або заповнити() це один раз, а не визначати і креслення кожної supbath одночасно. Беручи графік профилировщика описано на кроці 4 в якості прикладу, кожна одиночна вертикальна синя лінія-подпуть, а всі вони разом складають весь поточний шлях.

 Інсульт() метод в даний час називається цикл, який визначає кожен подпуть:

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
	}

 Цей графік можна зробити набагато більш ефективно, спочатку потрібно визначити всі подпути, то просто малював весь шлях відразу, як показано нижче.

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.

Крок 8: використовувати внеэкранный полотно для побудови сцени 

 Цей метод оптимізації відноситься на попередньому кроці, в тому, що вони обидва засновані на тому ж принципі мінімізації сторінці перефарбовує.

 Всякий раз, коли щось відбувається, що змінює вигляд чи зміст документа, браузер повинен запланувати операції перефарбовувати незабаром після цього на оновлення інтерфейсу. Перефарбовує може бути дорогою операцією з точки зору циклів процесора і потужність, особливо для щільних сторінок з великою кількістю елементів і анімації відбувається.  Якщо ви не вибудовуючи складні сцени анімації шляхом додавання безлічі предметів один раз у елементі , кожне нове доповнення може викликати цілий перефарбовувати.

 Краще і набагато швидше будувати сцену на екрані (у пам'яті) і після цього фарбувати всю сцену лише один раз на екрані, видимому елементі .

 Трохи нижче код, який отримує посилання на віджета і його контекст, ми додамо п'ять нових ліній, що створити внеэкранный полотно об'єкт DOM і зіставити його розміри з оригіналу, видно елемента .

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;

 Потім ми виконаємо пошук і заміна у всіх функціях малювання для всіх посилань на "mainCanvas" і змінити його на "osCanvas".  Посилання на "mainContext" буде замінено на "osContext". Все тепер буде звернено на нові закадрові полотно, замість оригінального елемента .

 Нарешті, ми додаємо ще одну рядок в Main (), яка малює те, що в даний час на екрані за допомогою елемента в наших оригінальних елемента .

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
}

Крок 9: Шляхи Кешу У Вигляді Растрових Зображень, Коли Це Можливо 

 Для багатьох видів графіки, за допомогою методу drawImage() буде набагато швидше, ніж будувати одні і ті ж зображення на полотно за допомогою шляху. Якщо ви виявите, що велика зілля скрипта витрачається постійно малював один і той же форм і обрисів, знову і знову, ви можете зберегти певну роботу браузера шляхом кешування отриманий малюнок в якості растрового зображення, малювати його тільки один раз на полотні, коли потрібно за допомогою методу drawImage().

Є два способи зробити це. 

 Перший-шляхом створення зовнішнього файлу зображення у форматі JPG, GIF або PNG зображення, а потім завантажувати його динамічно за допомогою JavaScript і скопіювавши його на полотно. Єдиний недолік цього методу-це додаткові файли, програму доведеться завантажити з мережі, але в залежності від типу графіка або що ваш додаток робить, це може виявитися гарним рішенням.  Віджет анімації використовує цей метод, щоб завантажити спінінг лезо графіки, які неможливо було б відтворити з допомогою всього шляху полотні функції малювання.

Другий спосіб передбачає якраз після малювання графіки на екрані замість завантаження зовнішнього зображення. Ми будемо використовувати цей метод для кешування назва віджета анімації.  Спочатку ми створюємо змінну для посилання на нове закадрове елемент canvas, який буде створений. Його значення за замовчуванням-false, так що ми можемо сказати, є чи не кешувати зображення був створений і зберігається після того, як скрипт починає виконуватися: 

1
 
2
	var titleCache = false; // points to an off-screen canvas used to cache the animation scene's title

 Потім ми відредагуємо drawTitle (функцій) у перший перевірити titleCache зображення полотно було створено. Якщо це не так, він створює образ поза екрану і зберігає посилання на нього в 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
}

Крок 10: очистити полотно з методу clearrect() 

 Першим кроком у розробці нового кадру анімації, щоб очистити полотно від поточного. Це можна зробити або скидання ширини елемента canvas, або з допомогою методу clearrect() функція.

 Скидання ширину має побічний ефект також, очищення поточного полотно контексту за замовчуванням, який може пригальмувати. За допомогою методу clearrect() завжди швидше і краще, щоб очистити полотно.

 У функції main (), ми змінимо це:

1
 
2
	osCanvas.width = osCanvas.width; //clear the off-screen canvas

...на це: 

1
 
2
	osContext.clearRect( 0, 0, osCanvas.width, osCanvas.height ); //clear the offscreen canvas

Крок 11: Створення Шарів 

 Якщо ви працювали з зображення або відео-редагування програмного забезпечення, як Gimp або Photoshop раніше, то ви вже знайомі з поняттям "шари", де зображення складається з укладання багато зображень поверх один одного, і кожен з них може бути обраний і змінений окремо.

 Наноситься на полотно сцени анімації, кожен шар буде окремий елемент canvas, розміщені на верхній частині один з одним за допомогою CSS, щоб створити ілюзію одного елемента. В якості методу оптимізації, це працює найкраще, коли є чітке розмежування між переднього плану і фону елементів сцени, причому більша частина дій відбувається на передньому плані.  Фоновому режимі, потім можна нанести на полотно елемент, який не сильно змінюється між кадрами анімації, а на передньому плані ще більш динамічний елемент canvas над ним. Таким чином, вся сцена не повинна бути знову перефарбували в кожному кадрі анімації.

 На жаль, віджет анімації-це хороший приклад сцени, де ми не можемо з користю застосувати цей метод, так як обидва переднього плану і фону елементів сильно пожвавлюються.


Крок 12: оновити тільки зміни областей сцени анімації 

 Це ще один метод оптимізації, який значною мірою залежить від складу анімацію сцени.  Він може бути використаний, коли сцена анімації концентрується навколо певної прямокутну область на полотні. Тоді ми могли б ясно і просто перекроїти перекроїти цей регіон.

 Наприклад, назва Синтел залишається незмінним протягом більшої частини анімації, так що ми могли б залишити це куточок незайманої, коли знявши полотно на наступний кадр анімації.

 Для реалізації цієї технології, ми замінюємо рядки, які викликає назва функції малювання в Main() з допомогою наступних блоків:

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
	}

Крок 13: Мінімізувати Субпиксельный Рендеринг 

 Субпиксельный рендеринг і згладжування відбувається, коли браузер автоматично застосовує графічні ефекти, щоб видалити нерівні краї. Це призводить до згладжування перегляд зображень і анімації, і автоматично включається всякий раз, коли ви вказати дробові координати, а не ціле число для нанесення на полотно.

 Зараз немає стандарту на те, як це повинно бути зроблено, так субпиксельного візуалізації трохи непослідовно в різних браузерах, з точки зору візуалізації.  Він також уповільнює швидкість рендеринга в браузері повинен зробити деякі розрахунки, щоб створити ефект. Як полотно згладжування не може бути безпосередньо відключена, єдиний спосіб обійти це з допомогою цілих чисел на кресленні координати.

 Ми використовуємо математику.підлогу() для забезпечення цілого числа в наш сценарій, коли це застосовно. Наприклад, наступний рядок у drawFilm():

1
 
2
	punchX = sample.x + ( j * punchInterval ) + 5; // the x co-ordinate

...переписана як: 

1
 
2
	punchX = sample.x + ( j * punchInterval ) + 5; // the x co-ordinate

Крок 14: виміряти результати 

 Ми переглянули чимало полотно методи оптимізації анімації, і його зараз час, щоб розглянути результати.

Ця таблиця показує до і після середнього часу render і полотно ФПС.   Ми можемо побачити деякі значні поліпшення у всіх браузерах, але це тільки хром, що насправді наближається до досягнення нашої початкової метою максимальної 33ms час рендеринга. Це означає, що ще багато належить зробити для досягнення цієї мети.

 Ми могли б продовжити, застосовуючи більш загальні методи оптимізації JavaScript, а якщо і це не допомагає, можливо, розглянути зменшити анімації, видаливши деякі навороти. Але ми не будемо дивитися на інші методи сьогодні, як основну увагу було приділено оптимізації для елемента анімація.

Полотно API-це досить новий і росте кожен день, тому продовжуйте експериментувати, тестування, вивчення і обмін. Спасибі за читання підручника. 

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.