HTML5 Canvas Optimization: en praktisk Example

HTML5 Canvas Optimization: Et praktisk eksempel
4
Del
8
Del
Dette Cyber ​​mandag Envato Tuts + kurs vil bli redusert til bare $ 3. Ikke gå glipp av.

Hvis du har gjort Javascript utvikling lenge nok, har du mest sannsynlig krasjet nettleseren din et par ganger. Problemet blir vanligvis ut til å være noen Script bug, som en endeløs mens loop; hvis ikke, er det neste mistenkte siden transformasjoner eller animasjoner - den typen som involverer å legge til og fjerne elementer fra nettsiden eller animere CSS stil egenskaper. Denne opplæringen fokuserer på å optimalisere animasjoner produsert ved hjelp av JS og HTML5 < lerret > element.


Denne opplæringen starter og slutter med hva HTML5 animasjon widgeten du se nedenfor:

Vi vil ta det med oss ​​på en reise, utforske de forskjellige vekst lerret optimaliseringstips og teknikker og bruke dem til widgeten Javascript kildekode. Målet er å forbedre på widgeten gjennomføring hastighet og ende opp med en mykere, mer flytende animasjon widget, drevet av slankere, mer effektiv Javascript.

Kilden nedlastingen inneholder HTML og Javascript fra hvert trinn i opplæringen , slik at du kan følge med fra et punkt

La oss ta det første skrittet



Trinn 1:.. Spill Movie Trailer

Widgeten ovenfor er basert på filmen trailer for Sintel, en 3D-animert film av Blender Foundation. Det er bygget ved hjelp av to av HTML5 mest populære tilskudd: < lerret > og < video > elementer

<. video > laster og spiller Sintel videofil, mens < lerret > genererer sin egen animasjonssekvens ved å ta bilder av å spille video og blande det med tekst og annen grafikk. Når du klikker for å spille av videoen, springer lerretet til liv med en mørk bakgrunn som er en større svart og hvitt kopi av spille video. Mindre, fargede skjermbilder av videoen er kopiert til scene, og glir over det som en del av en filmrull illustrasjon.

I øvre venstre hjørne har vi tittelen og noen få linjer med beskrivende tekst som fade inn og ut som animasjon spiller. Manusets ytelse hastighet og relaterte beregninger inngår som en del av animasjon, i den lille svarte boksen nederst i venstre hjørne med en graf og levende tekst. Vi skal se på dette bestemt element i mer detalj senere.

Til slutt, det er en stor roterende blad som flyr over scenen på begynnelsen av animasjon, som grafisk lastes fra en ekstern PNG bildefil.



Trinn 2: Vise Kilde

Kildekoden inneholder den vanlige blandingen av HTML, CSS og Javascript. HTML er sparsom: bare < lerret > og < video > koder, omsluttet av en container < div >:
< div id = "animationWidget" > < lerret width = "368" height = "208" id = "mainCanvas" > < /lerret > < video width = "184" height = "104" id = "Video" autobuffer = "autobuffer" kontroller = "kontroller" plakat = "poster.jpg" > < kilde src = "sintel.mp4" type = "video /mp4" > < /kilde > < kilde src = "sintel.webm" type = "video /WebM" > < /kilde > < /video > < /div >

Beholderen < div > gis en ID (animationWidget), som fungerer som en krok for alle CSS-reglene som anvendes til det og dens innhold (nedenfor)
#animationWidget {border:. 1px # 222 fast; position: relative; width: 570px; høyde: 220px;} # animationWidget lerret {border: 1px # 222 fast; position: absolute; top: 5px; venstre: 5px; } #animationWidget video {position: absolute; top: 110px; venstre: 380px; }

Mens HTML og CSS er marinert krydder og krydder, dens Javascript som er kjøtt av widget.

På toppen, har vi de viktigste objektene som skal brukes ofte gjennom script, inkludert referanser til lerretet element og dens 2D sammenheng.

init () funksjonen kalles når videoen begynner å spille, og setter opp alle objekter som brukes i manuset.

sampleVideo () -funksjonen fanger den nåværende rammen av å spille video, mens setBlade () laster en ekstern bilde kreves av animasjon.

Tempoet og innholdet i lerretet animasjonen er kontrollert av main () -funksjonen, som er som manuset hjerterytme. Kjøres med jevne mellomrom når videoen begynner å spille, det tegner hver ramme av animasjon ved først å tømme lerretet, deretter ringer hver og en av skriptet fem tegning functions:

drawBackground()

drawFilm()

drawTitle()

drawDescription()

drawStats()



As de navnene antyder, hver tegning funksjonen er ansvarlig for å tegne et element i animasjonen scene. Strukturere koden på denne måten forbedrer fleksibilitet og gjør fremtidig vedlikehold enklere.

Hele manuset er vist nedenfor. Ta deg tid til å vurdere det, og se om du kan oppdage eventuelle endringer du vil gjøre for å fremskynde den opp plakater (function () {if (document.createElement ("lerret") getContext) {return;.!.} //lerretet tag ikke støttes Var mainCanvas = document.getElementById ("mainCanvas"); //peker til HTML canvas-elementet ovenfor Var mainContext = mainCanvas.getContext ('2d'); //tegningen kontekst av lerretet element Var video = document.getElementById ("video"); //peker til HTML video element Var frameDuration = 33; //animasjonen hastighet i millisekunder video.addEventListener ('play', init); //The init () -funksjonen kalles når brukeren trykker spille & videoen begynner /fortsetter å spille video.addEventListener ('endte', function () {drawStats (true);}); //drawStats () kalles en siste gang når videoen slutten, å oppsummere alle statistikker Var videoSamples; //dette er en rekke bilder, som brukes til å lagre alle øyeblikksbilder av å spille video tatt over tid Disse bildene er brukt til å lage "film hjul 'Var bakgrunn.; //Dette er en rekke bilder, som brukes til å lagre alle øyeblikksbilder av å spille video tatt over tid. Disse bildene blir brukt som lerret bakgrunn Var blad; //Et lerret element for å lagre bildet kopiert fra blade.png Var bladeSrc = 'blade.png'; //banen til bladets image kildefilen Var lastPaintCount = 0; //Lagrer den siste verdien av mozPaintCount samplet Var paintCountLog = []; //En matrise som inneholder alle målte verdier av mozPaintCount over tid Var speedLog = []; //En matrise som inneholder alle de henrettelsen hastigheter på main (), målt i millisekunder Var fpsLog = []; //En matrise som inneholder de beregnede rammer per Secong (fps) i skriptet, målt ved å telle samtaler til main () per sekund Var frameCount = 0; //Teller antall ganger hoved () er utført per sekund. Var frameStartTime = 0; //Siste gang main () ble kalt //Kalt når videoen spilles av. Setter opp alle script objektene som kreves for å generere lerretet animasjon og måle perfomance funksjonen init () {if (video.currentTime > 1) {return; } BladeSrc = new Image (); bladeSrc.src = "blade.png"; bladeSrc.onload = setBlade; bakgrunn = []; videoSamples = []; fpsLog = []; paintCountLog = []; if (window.mozPaintCount) {lastPaintCount = window.mozPaintCount; } SpeedLog = []; frameCount = 0; frameStartTime = 0; hoved(); setTimeout (getStats, 1000); } //Som skript hovedfunksjon, den styrer tempoet i animasjonen funksjonen main () {setTimeout (hoved, frameDuration); if (video.paused || video.ended) {return; } Var nå = new Date () getTime (.); if (frameStartTime) {speedLog.push (nå - frameStartTime); } FrameStartTime = nå; if (video.readyState < 2) {return; } FrameCount ++; mainCanvas.width = mainCanvas.width; //tømme lerretet drawBackground (); drawFilm (); drawDescription (); drawStats (); drawBlade (); drawTitle (); } //Er Denne funksjonen kalles hvert sekund, og den beregner og lagrer de gjeldende bildefrekvens funksjons getStats () {if (video.readyState > = 2) {if (window.mozPaintCount) {//denne eiendommen er spesifikk for firefox og spor hvor mange ganger leseren har ytet vinduet siden dokumentet ble lastet paintCountLog.push (window.mozPaintCount - lastPaintCount); lastPaintCount = window.mozPaintCount; } FpsLog.push (frameCount); frameCount = 0; } SetTimeout (getStats, 1000); } //Lage blad, de ofscreen canavs som skal inneholde spining animasjon av bildet kopiert fra blade.png funksjon setBlade () {bladet = document.createElement ("lerret"); blade.width = 400; blade.height = 400; blade.angle = 0; blade.x = -blade.height * 0,5; blade.y = mainCanvas.height /2 - blade.height /2; } //Oppretter og returnerer et nytt bilde som inneholder et øyeblikksbilde av videoen som spilles. fungere sampleVideo () {var newCanvas = document.createElement ("lerret"); newCanvas.width = video.width; newCanvas.height = video.height; newCanvas.getContext ("2d") drawImage (video, 0, 0, video.width, video.height.); returnere newCanvas; } //Gjengir den mørke bakgrunnen for hele lerretet element. Bakgrunnen har en gråskala prøve av video og en gradient overlay funksjon drawBackground () {var newCanvas = document.createElement ("lerret"); Var newContext = newCanvas.getContext ("2d"); newCanvas.width = mainCanvas.width; newCanvas.height = mainCanvas.height; newContext.drawImage (video, 0, video.height * 0.1, video.width, video.height * 0,5, 0, 0, mainCanvas.width, mainCanvas.height); Var imageData data; try {imageData = newContext.getImageData (0, 0, mainCanvas.width, mainCanvas.height); data = imageData.data; } Catch (feil) {//kor feil (f.eks sett fra en lokal fil). Lag en solid fylle bakgrunnen i stedet newContext.fillStyle = "gul"; newContext.fillRect (0, 0, mainCanvas.width, mainCanvas.height); imageData = mainContext.createImageData (mainCanvas.width, mainCanvas.height); data = imageData.data; } //Sløyfe gjennom hver piksel, snu sin farge i en nyanse av grått for (var i = 0; i < data.length; i + = 4) {var rød = data [i]; Var grønn = data [i + 1]; Var blå = data [i + 2]; Var grå = Math.max (rød, grønn, blå); data [i] = grå; data [i + 1] = grå; data [i + 2] = grå; } NewContext.putImageData (imageData, 0, 0); //legge gradient overlay Var gradient = newContext.createLinearGradient (mainCanvas.width /2, 0, mainCanvas.width /2, mainCanvas.height); gradient.addColorStop (0, '# 000'); gradient.addColorStop (0,2, '# 000'); gradient.addColorStop (1 ", RGBA (0,0,0,0.5)"); newContext.fillStyle = gradient; newContext.fillRect (0, 0, mainCanvas.width, mainCanvas.height); mainContext.save (); mainContext.drawImage (newCanvas, 0, 0, mainCanvas.width, mainCanvas.height); mainContext.restore (); } //Gjengir 'film hjul "animasjon som ruller over lerretet funksjon drawFilm () {var sampleWidth = 116; //Bredden på en samplet videobilde, når malt på lerret som en del av en "filmspolen 'Var sampleHeight = 80; //Høyden av en samplet videobilde, når malt på lerret som en del av en "filmspolen 'Var filmSpeed ​​= 20; //Bestemmer hvor raskt 'film Reel' ruller over den genererte lerret animasjon. Var filmTop = 120; //y koordinat av "film hjul" animasjon Div filmAngle = -10 * Math.PI /180; //synspunkt på 'film hjul' Var filmRight = (videoSamples.length > 0)? videoSamples [0] .x + videoSamples.length * sampleWidth: mainCanvas.width; //den høyre kanten av 'film hjul' i piksler, i forhold til lerretet //her sjekker vi om det første bildet av den "film hjul" har rullet ut av syne hvis (videoSamples.length > 0) {var bottomLeftX = videoSamples [0] .x + sampleWidth; Var bottomLeftY = filmTop + sampleHeight; bottomLeftX = Math.floor (Math.cos (filmAngle) * bottomLeftX - tak i Math.sin (filmAngle) * bottomLeftY); //Den endelige visningsposisjon etter rotasjon if (bottomLeftX < 0) {//rammen er offscreen, fjerne det refference fra filmen matrisen videoSamples.shift (); }} //Legge til nye rammer til hjul som nødvendig så lenge (filmRight < = mainCanvas.width) {var newFrame = {}; newFrame.x = filmRight; newFrame.canvas = sampleVideo (); videoSamples.push (newFrame); filmRight + = sampleWidth; } //Lage gradient fill for hjul Var gradient = mainContext.createLinearGradient (0, 0, mainCanvas.width, mainCanvas.height); gradient.addColorStop (0, '# 0D0D0D'); gradient.addColorStop (0,25, '# 300A02'); gradient.addColorStop (0,5, '# AF5A00'); gradient.addColorStop (0,75, '# 300A02'); gradient.addColorStop (1, '# 0D0D0D'); mainContext.save (); mainContext.globalAlpha = 0,9; mainContext.fillStyle = gradient; mainContext.rotate (filmAngle); //Looper gjennom alle elementer av film array, bruker de lagrede koordinere verdier av hvert å trekke en del av "film hjul" for (var i i videoSamples) {var prøven = videoSamples [i]; Var punchX, punchy, punchWidth = 4, punchHeight = 6, punchInterval = 11,5; //trekker hoved rektangulære boksen av prøven mainContext.beginPath (); mainContext.moveTo (sample.x, filmTop); mainContext.lineTo (sample.x + sampleWidth, filmTop); mainContext.lineTo (sample.x + sampleWidth, filmTop + sampleHeight); mainContext.lineTo (sample.x, filmTop + sampleHeight); mainContext.closePath (); //legger de små hullene langs øvre og nedre kant av 'fim hjul "for (var j = 0; j < 10; j ++) {punchX = sample.x + (j * punchInterval) + 5; punchy = filmTop + 4; mainContext.moveTo (punchX, punchy + punchHeight); mainContext.lineTo (punchX + punchWidth, punchy + punchHeight); mainContext.lineTo (punchX + punchWidth, punchy); mainContext.lineTo (punchX, punchy); mainContext.closePath (); punchX = sample.x + (j * punchInterval) + 5; punchy = filmTop + 70; mainContext.moveTo (punchX, punchy + punchHeight); mainContext.lineTo (punchX + punchWidth, punchy + punchHeight); mainContext.lineTo (punchX + punchWidth, punchy); mainContext.lineTo (punchX, punchy); mainContext.closePath (); } MainContext.fill (); } //Sløyfe gjennom alle elementer av videoSamples array, oppdatere x koordinere verdier av hvert element, og trekke sin lagret bilde på lerretet mainContext.globalCompositeOperation = "lettere"; for (var i i videoSamples) {var prøven = videoSamples [i]; sample.x - = filmSpeed; mainContext.drawImage (sample.canvas, sample.x + 5, filmTop + 10, 110, 62); } MainContext.restore (); } //Gjengir lerretet tittelen funksjon drawTitle () {mainContext.save (); mainContext.fillStyle = "svart"; mainContext.fillRect (0, 0, 368, 25); mainContext.fillStyle = "hvite"; mainContext.font = "bold 21px Georgia"; mainContext.fillText ("Sintel", 10, 20); mainContext.restore (); } //Gjengir all tekst vises øverst i venstre hjørne av lerretet funksjon drawDescription () {var text = []; //lagrer alle tekstelementer som skal vises over tid. videoen er 60 sekunder, og alle vil være synlig i 10 sekunder. tekst [0] = "Sintel er en uavhengig produsert kortfilm, initiert av Blender Foundation."; tekst [1] = "For over et år et internasjonalt team av 3D-animatører og kunstnere jobbet i studio i Amsterdam Blender Institute på dataanimert kort 'Sintel'."; tekst [2] = "Det er en episk kortfilm som foregår i en fantasiverden, der en jente blir venn med en baby drage."; tekst [3] = "Når den lille dragen er tatt fra henne voldsomt, foretar hun en lang reise som fører henne til en dramatisk konfrontasjon."; tekst [4] = "Manuset ble inspirert av en rekke historie forslag fra Martin Lodewijk rundt en Askepott karakter (Cinder på nederlandsk er" Sintel '). "; tekst [5] = "Manusforfatter Esther Wouda deretter jobbet med regissør Colin Levy å lage et manus med flere lag, med sterk karakterisering og dramatisk innvirkning som sentrale mål."; text = teksten [Math.floor (video.currentTime /10)]; //bruker videoer gjeldende tid å finne ut hvilke tekstelement som skal vises. mainContext.save (); Var alfa = 1 - (video.currentTime% 10) /10; mainContext.globalAlpha = (alfa < 5)? alpha: 1; mainContext.fillStyle = '#fff'; mainContext.font = "normal 12px Georgia"; //bryte teksten opp i flere linjer etter behov, og skrive hver linje på lerretet text = text.split (''); Var colWidth = mainCanvas.width * 0,75; Var linje = ''; Var y = 40; for (var i tekst) {linje + = text [i] + ''; if (mainContext.measureText (linje) .width > colWidth) {mainContext.fillText (linje 10, y); linje = ''; + y = 12; }} MainContext.fillText (line, 10, y); mainContext.restore (); } //Oppdaterer nederst til høyre potion av lerretet med de nyeste perfomance statistikkfunksjon drawStats (gjennomsnitt) {var x = 245,5, y = 130,5, graphScale = 0,25; mainContext.save (); mainContext.font = "normal 10px monospace"; mainContext.textAlign = "venstre"; mainContext.textBaseLine = 'top'; mainContext.fillStyle = "svart"; mainContext.fillRect (x, y, 120, 75); //trekke x- og y-akselinjer grafen + y = 30; x + = 10; mainContext.beginPath (); mainContext.strokeStyle = '# 888'; mainContext.lineWidth = 1,5; mainContext.moveTo (x, y); mainContext.lineTo (x + 100, y); mainContext.stroke (); mainContext.moveTo (x, y); mainContext.lineTo (x, y - 25); mainContext.stroke (); //Trekke de siste 50 speedLog oppføringer på grafen mainContext.strokeStyle = '# 00FFFF'; mainContext.fillStyle = '# 00FFFF'; mainContext.lineWidth = 0,3; Var imax = speedLog.length; var i = (speedLog.length > 50)? speedLog.length - 50: 0 mainContext.beginPath (); for (var j = 0; i < imax; i ++, j + = 2) {mainContext.moveTo (x + j, y); mainContext.lineTo (x + j, y - speedLog [i] * graphScale); mainContext.stroke (); } //Den røde linjen, for å markere ønsket maksimal gjengivelse tid mainContext.beginPath (); mainContext.strokeStyle = '# FF0000'; mainContext.lineWidth = 1; Var target = y - frameDuration * graphScale; mainContext.moveTo (x, target); mainContext.lineTo (x + 100, target); mainContext.stroke (); //Aktuell /gjennomsnittlig speedLog elementer y + = 12; if (gjennomsnitt) {var speed = 0; for (i i speedLog) {speed + = speedLog [i]; } Hastighet = Math.floor (hastighet /speedLog.length * 10) /10; } else {hastighet = speedLog [speedLog.length-1]; } MainContext.fillText ('Render Tidspunkt:' + hastighet, x, y); //Lerret fps mainContext.fillStyle = '# 00FF00'; + y = 12; if (gjennomsnitt) {fps = 0; for (i i fpsLog) {fps + = fpsLog [i]; } Fps = Math.floor (fps /fpsLog.length * 10) /10; } else {fps = fpsLog [fpsLog.length-1]; } MainContext.fillText ('Canvas FPS:' + fps, x, y); //Leser bilder per sekund (fps), ved hjelp av window.mozPaintCount (firefox only) if (window.mozPaintCount) {y + = 12; if (gjennomsnitt) {fps = 0; for (i i paintCountLog) {fps + = paintCountLog [i]; } Fps = Math.floor (fps /paintCountLog.length * 10) /10; } else {fps = paintCountLog [paintCountLog.length-1]; } MainContext.fillText ('Browser FPS:' + fps, x, y); } MainContext.restore (); } //Trekke spining blad som vises i begynnelsen av animasjon funksjonen drawBlade () {if (blad || blade.x >! MainCanvas.width) {return; } Blade.x + = 2,5; blade.angle = (blade.angle - 45)% 360; //oppdatering blad, en ofscreen lerret inneholder bladets image Var vinkel = blade.angle * Math.PI /180; Var bladeContext = blade.getContext ('2d'); blade.width = blade.width; //tømme lerretet bladeContext.save (); bladeContext.translate (200, 200); bladeContext.rotate (vinkel); bladeContext.drawImage (bladeSrc, -bladeSrc.width /2, -bladeSrc.height /2); bladeContext.restore (); mainContext.save (); mainContext.globalAlpha = 0,95; mainContext.drawImage (blad, blade.x, blade.y + tak i Math.sin (vinkel) * 50); mainContext.restore (); }}) ();



Trinn 3: Code optimalisering: kjenner reglene

Den første regelen med kode ytelsesoptimalisering er: ikke.

Poenget med denne regelen er å hindre optimalisering for optimalisering skyld, siden prosessen kommer til en pris.

En svært optimalisert skriptet vil være lettere for leseren å analysere og prosess, men vanligvis med en byrde for mennesker som vil finne det vanskeligere å følge og opprettholde. Når du bestemmer deg for at noen optimalisering er nødvendig, sette noen mål på forhånd slik at du ikke bli båret bort av prosessen og overdrive det.

Målet i å optimalisere denne widgeten vil være å ha main () Funksjonen kjøres på mindre enn 33 millisekunder som det skal, noe som vil matche bildefrekvens på spillevideofiler (sintel.mp4 og sintel.webm). Disse filene ble kodet på en avspillingshastighet på 30 bilder i sekundet (tretti bilder per sekund), som kan oversettes til ca 0,33 sekunder eller 33 millisekunder per ramme (1 sekund ÷ 30 bilder).

Siden Java trekker en ny animasjon ramme til lerretet hver gang main () funksjonen kalles, vil målet for vår optimalisering prosessen være å gjøre denne funksjonen ta 33 millisekunder eller mindre hver gang det kjører. Denne funksjonen kaller seg gjentatte ganger å bruke en setTimeout () Javascript timer som vist nedenfor
Var frameDuration = 33.; //Satt animasjonen mål fart i millisekunder funksjonen main () {if (video.paused || video.ended) {return false; } SetTimeout (hoved, frameDuration);
Den andre regelen: Har ikke ennå.

Denne regelen understreker poenget at optimalisering bør alltid gjøres på slutten av utviklingsprosessen når du allerede har fleshed ut noen komplett, arbeider kode. Optimalisering Politiet vil la oss gå på dette, siden widget manus er et perfekt eksempel på komplett, arbeidsprogram som er klar for prosessen

Den tredje regelen. Har ennå ikke, og profilere først.

Denne regelen er om å forstå programmet i form av runtime ytelse. Profilering hjelper deg vet heller enn gjette hvilke funksjoner eller områder av manuset tar opp mest tid eller blir brukt oftest, slik at du kan fokusere på de i optimaliseringsprosessen. Det er viktig nok til å gjøre ledende nettlesere skip med innebygde Javascript profilerings, eller har extensions som tilbyr denne tjenesten.

Jeg løp widgeten under profiler i Firebug, og nedenfor er et skjermbilde av resultatene. Anmeldelser



Trinn 4: Angi Noen ytelsesmålinger

Som du kjørte widget, er jeg sikker på at du fant alle Sintel ting i orden, og ble helt blåst bort av elementet på nedre høyre hjørne av lerretet, den ene med en vakker graf og skinnende tekst

Det er ikke bare et pent ansikt.; den boksen leverer også noen real-time ytelse statistikk på løpeprogrammet. Dens faktisk en enkel, bare-bones Javascript profiler. Det er riktig! Yo, jeg hørte du liker profilering, så jeg satte en profiler i filmen din, slik at du kan profilere det mens du ser på.

Grafen sporer Render Tid
, beregnes ved å måle hvor lenge hver kjøring av main () tar i millisekunder. Siden dette er den funksjonen som trekker hver ramme av animasjon, er det effektivt animasjonen er bildefrekvensen. Hver vertikal blå linje i diagrammet illustrerer tiden tatt av en ramme. Den røde horisontale linjen er målet hastighet, som vi satt på 33ms å matche videofilen bildefrekvens. Rett under grafen, er hastigheten på den siste samtalen til main () gitt i millisekunder.

profiler er også en hendig leseren gjengivelse hastighet test. I øyeblikket den gjennomsnittlige gjengi tid i Firefox er 55ms, 90ms i IE 9, 41ms i Chrome, 148ms i Opera og 63ms i Safari. Alle nettlesere ble kjører på Windows XP, bortsett fra IE 9, som ble profilert på Windows Vista.

Den neste beregningen nedenfor som er Canvas FPS plakater (canvas bilder per sekund), oppnådd ved å telle hvor mange ganger main () kalles per sekund. Profil viser de siste Canvas FPS hastighet når videoen er fortsatt spiller, og når det ender det viser gjennomsnittlig hastighet på alle samtaler til main ().

Den siste beregningen er Browser FPS Anmeldelser , som måler hvor mange nettleseren skjermoppdateringen gjeldende vindu hvert sekund. Dette er bare tilgjengelig hvis du vil vise widgeten i Firefox, så det avhenger av en funksjon for øyeblikket bare på at browseren kalt window.mozPaintCount., En Javascript eiendom som holder styr på hvor mange ganger nettleservinduet har blitt malt siden nettsiden først lastet.

Maler vanligvis oppstår når en hendelse eller en handling som endrer utseendet på en side oppstår, som når du blar nedover siden eller musen over en link. Det er effektivt nettleserens real bildefrekvens, som bestemmes av hvor opptatt aktuelle nettsiden er.

For å måle hvilken effekt den un-optimalisert lerret animasjon hadde på mozPaintCount, fjernet jeg lerretet tag og alle Javascript, så som å spore nettleseren bildefrekvens når du spiller bare video. Mine tester ble gjort i Firebug konsollen ved hjelp av funksjonen under:
Var lastPaintCount = window.mozPaintCount; setInterval (function () {console.log (window.mozPaintCount - lastPaintCount); lastPaintCount = window.mozPaintCount;}, 1000);

Resultatene: Nettleseren frame rate var mellom 30 og 32 FPS da videoen ble spilt, og falt til 0-1 FPS når videoen ble avsluttet. Dette betyr at Firefox var å justere sitt vindu repaint frekvens for å matche det av å spille video, kodet på 30fps. Når testen ble kjørt med un-optimalisert lerret animasjon og video spille sammen, det bremset ned til 16fps, som nettleseren ble nå sliter med å kjøre hele Script og fortsatt male sitt vindu på gang, noe som gjør både videoavspilling og lerret animasjoner svak.

Vi skal nå begynne å tilpasse programmet vårt, og når vi gjør det, vil vi holde styr på Render Time, Canvas FPS og Browser FPS for å måle effekten av våre endringer.
< hr>
Trinn 5: Bruk requestAnimationFrame ()

De to siste Script snutter ovenfor gjør bruk av setTimeout () og setInterval () timerfunksjoner. Hvis du vil bruke disse funksjonene, kan du angi et tidsintervall i millisekunder og tilbakeringingsfunksjonen du vil ha utført etter at tiden er omme. Forskjellen mellom de to er at setTimeout () vil kalle din funksjon bare én gang, mens setInterval () kaller det gjentatte ganger.

Selv om disse funksjonene har alltid vært uunnværlige verktøy i Javascript-animator kit, de har noen feil:

Først er tidsintervallet sett ikke alltid pålitelige. Hvis programmet er fortsatt i midten av utførende noe annet når intervallet utløper, vil tilbakeringingsfunksjonen skal utføres senere enn opprinnelig satt, når nettleseren er ikke lenger opptatt. I main () funksjonen, setter vi intervallet til 33 millisekunder - men som profil avslører, er funksjonen faktisk kalt hver 148 millisekunder i Opera

For det andre, det er et problem med nettleseren skjermoppdateringen.. Hvis vi hadde en tilbakeringingsfunksjonen som genererte 20 animasjons bilder per sekund mens nettleser malt vinduet sitt bare 12 ganger i sekundet, vil 8 samtaler til den funksjonen være bortkastet som brukeren aldri vil komme til å se resultatene.

Endelig har nettleseren ikke vite at funksjonen blir kalt er animere elementer i dokumentet. Dette betyr at hvis disse elementene rulle ut av visningen, eller brukeren klikker på en annen fane, tilbakeringing vil fortsatt få utført gjentatte ganger, sløse CPU-sykluser.

Ved hjelp requestAnimationFrame () løser de fleste av disse problemene, og det kan brukes i stedet for de timerfunksjoner i HTML5 animasjoner. I stedet for å angi et tidsintervall, requestAnimationFrame () synkroniserer funksjonskall med nettleservindu skjermoppdateringen. Dette resulterer i mer væske, konsekvent animasjon som ingen rammer er droppet, og leseren kan gjøre ytterligere interne optimaliseringene vite en animasjon er i gang.

Hvis du vil erstatte setTimeout () med requestAnimationFrame i vår widget, må vi først legge til følgende linje på toppen av skriptet:
requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || setTimeout;

Som spesifikasjonen er fortsatt ganske ny, noen nettlesere eller nettleserversjoner har sine egne eksperimentelle implementeringer, gjør at denne linje som funksjonsnavnet peker til høyre metoden hvis den er tilgjengelig, og faller tilbake til setTimeout () hvis ikke. Så i main () -funksjonen, endrer vi denne linjen:
setTimeout (hoved, frameDuration);

... til:
requestAnimationFrame (hoved, lerret);

Den første parameteren tar tilbakeringing funksjon, som i dette tilfelle er hoved () -funksjonen. Den andre parameteren er valgfri, og angir DOM element som inneholder animasjonen. Det er ment å bli brukt av å beregne ytterligere optimaliseringer.

Merk at getStats () -funksjonen bruker også en setTimeout (), men vi la den ene på plass siden denne funksjonen ikke har noe å gjøre med animere scene. requestAnimationFrame () ble opprettet spesielt for animasjoner, så hvis din tilbakeringingsfunksjonen ikke gjør animasjon, kan du fortsatt bruke setTimeout () eller setInterval ()



Trinn 6:. Bruk Side Sikt API

I det siste trinnet vi gjort requestAnimationFrame makt lerretet animasjon, og nå har vi et nytt problem. Hvis vi begynner å kjøre widgeten, deretter minimere nettleservinduet eller bytte til en ny fane, struper widgeten vindu repaint hastigheten ned for å spare strøm. Dette bremser også ned lerretet animasjon siden det er nå synkronisert med repaint rate -. Som ville være perfekt hvis videoen ikke fortsette å spille på til slutten

Vi trenger en måte å oppdage når siden er ikke blir sett slik at vi kan pause avspillingen video; det er her Page Sikt API kommer til unnsetning.

API inneholder et sett av egenskaper, funksjoner og hendelser vi kan bruke til å oppdage om en webside er i lys eller skjult. Vi kan da legge til kode som justerer vårt program atferd deretter. Vi vil gjøre bruk av denne APIen for å stanse widget spillestil video når siden er inaktiv

Vi starter ved å legge til en ny hendelse lytteren til vår script.
Document.addEventListener ('visibilitychange', onVisibilityChange, false);

Så kommer hendelsesbehandling funksjonen:
//Justerer program atferd, basert på om websiden er aktiv eller hiddenfunction onVisibilityChange () {if (document.hidden & & video.paused) {video.pause (); } else if (video.paused) {video.play (); }}



Trinn 7: For Custom Shapes, Tegn hele banen At gang

Paths brukes til å lage og tegne egne figurer og skisserer på < lerret > element, som vil til enhver tid ha en aktiv bane.

innehar En sti en liste over under stier, og hvert sub-banen består av lerret koordinere punkter knyttet sammen av enten en linje eller en kurve. All banen gjør og tegnefunksjoner er egenskapene til lerretet kontekst objekt, og kan deles inn i to grupper.

Det er de subpath lage funksjoner, som brukes for å definere en subpath og inkluderer lineTo (), quadraticCurveTo ( ), bezierCurveTo (), og bue (). Da har vi hjerneslag () og fyll (), bane /subpath tegnefunksjoner. Ved hjelp av slag () vil produsere en disposisjon, mens fill () genererer en form fylt av enten en farge, gradient eller mønster.

Når du tegner former og omriss på lerretet, er det mer effektivt å lage hele banen første, så bare slag () eller fyll () det en gang, i stedet for å definere og å trekke hver supbath gangen. Tar profiler grafen er beskrevet i trinn 4 som et eksempel, er hver enkelt vertikalt blå linje en subpath, mens alle av dem sammen utgjør hele strømbanen.

slag () metoden blir nå kalles innen sløyfe som definerer hver subpath:
mainContext.beginPath (); for (var j = 0; i < imax; i ++, j + = 2) {mainContext.moveTo (x + j, y);