Het beheersen van fps met requestAnimationFrame?

Het lijkt alsof requestAnimationFrame is de de facto manier te animeren dingen nu. Het werkte behoorlijk goed voor mij voor het grootste deel, maar nu ik probeer te doen wat canvas animaties en ik vroeg me af: Is er een manier om te zorgen dat het loopt op een bepaalde fps? Ik begrijp dat het doel van de rAF is voor consistente vloeiende animaties, en ik zou het risico van het maken van mijn animatie schokkerig, maar nu lijkt het te draaien op een drastisch verschillende snelheden vrij willekeurig, en ik vraag me af of er een manier om de strijd tegen die op een of andere manier.

Ik zou gebruik maken van setInterval maar ik wil de optimalisaties die rAF biedt (met name automatisch te stoppen wanneer het tabblad in focus).

In het geval dat iemand wil kijken naar mijn code, het is vrij veel:

animateFlash: function() {
    ctx_fg.clearRect(0,0,canvasWidth,canvasHeight);
    ctx_fg.fillStyle = 'rgba(177,39,116,1)';
    ctx_fg.strokeStyle = 'none';
    ctx_fg.beginPath();
    for(var i in nodes) {
        nodes[i].drawFlash();
    }
    ctx_fg.fill();
    ctx_fg.closePath();
    var instance = this;
    var rafID = requestAnimationFrame(function(){
        instance.animateFlash();
    })

    var unfinishedNodes = nodes.filter(function(elem){
        return elem.timer < timerMax;
    });

    if(unfinishedNodes.length === 0) {
        console.log("done");
        cancelAnimationFrame(rafID);
        instance.animate();
    }
}

Waar Knooppunt.drawFlash() is slechts de code die bepaalt straal op basis van een teller variabele en vervolgens tekent een cirkel.

  • Heeft uw animatie lag? Ik denk dat het grootste voordeel van requestAnimationFrame is (zoals de naam van de soort doet vermoeden) een verzoek om een animatie frame alleen wanneer het nodig is. Laten we zeggen dat je een statische zwart canvas, krijgt u 0 fps, omdat er geen nieuwe frame is nodig. Maar als u een weergave van een animatie dat vereist 60fps, zou je dat ook. rAF enkel toe om te “springen” nutteloze frames en sla CPU.
  • setInterval werken niet in de inactieve tabblad ook.

 

8 Replies
  1. 151

    Hoe gashendel requestAnimationFrame naar een specifiek frame rate

    Demo beperking bij 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/

    Deze methode werkt door het testen van de verstreken tijd sinds het uitvoeren van de laatste frame lus.

    Uw tekening code alleen wordt uitgevoerd wanneer de door u opgegeven FPS-interval is verstreken.

    Het eerste deel van de code stelt een aantal variabelen die gebruikt zijn voor het berekenen van verstreken tijd.

    var stop = false;
    var frameCount = 0;
    var $results = $("#results");
    var fps, fpsInterval, startTime, now, then, elapsed;
    
    
    //initialize the timer variables and start the animation
    
    function startAnimating(fps) {
        fpsInterval = 1000 / fps;
        then = Date.now();
        startTime = then;
        animate();
    }

    En deze code wordt de werkelijke requestAnimationFrame lus die is gebaseerd op uw opgegeven FPS.

    //the animation loop calculates time elapsed since the last loop
    //and only draws if your specified fps interval is achieved
    
    function animate() {
    
        //request another frame
    
        requestAnimationFrame(animate);
    
        //calc elapsed time since last loop
    
        now = Date.now();
        elapsed = now - then;
    
        //if enough time has elapsed, draw the next frame
    
        if (elapsed > fpsInterval) {
    
            //Get ready for next frame by setting then=now, but also adjust for your
            //specified fpsInterval not being a multiple of RAF's interval (16.7ms)
            then = now - (elapsed % fpsInterval);
    
            //Put your drawing code here
    
        }
    }
    • Uitstekende uitleg en voorbeeld. Deze moeten gemarkeerd worden als de geaccepteerde antwoord
    • Leuke demo – moet worden aanvaard. Hier, gevorkte uw viool, aan te tonen met behulp van het venster.de prestaties.nu() in plaats van de Datum.nu(). Dit gaat lekker met de high-res tijdstempel dat rAF al ontvangt, dus er is geen noodzaak om te noemen Datum.nu() in de callback: jsfiddle.net/chicagogrooves/nRpVD/2
    • Bedankt voor de bijgewerkte link met de nieuwe rAF tijdstempel voorzien. De nieuwe rAF tijdstempel toegevoegd nuttig infrastruction en het is ook nauwkeuriger dan de Datum.nu is.
    • Dit is echt een leuke demo, die mij geïnspireerd om mijn eigen (JSFiddle). De belangrijkste verschillen zijn met rAF (zoals Dean ‘ s demo) in plaats van de Datum, de controls toe te voegen aan het dynamisch aanpassen van doel framerate, bemonstering framerate op een aparte interval van de animatie, en het toevoegen van een grafiek van historische framerates.
    • Dit is een ingenieuze oplossing– het enige probleem is dat het zorgt voor extra overhead in de RAF, en kan schadelijk zijn voor de eigenlijke frame-rate als gevolg van de data-manipulatie gaande is binnen de RAF. Om dit te omzeilen, houden de data manipulatie in een aparte setInterval, indien mogelijk in een web-werknemer heeft dus een eigen thread. Ideaal voor de RAF moet alleen update graphics, en lees js objecten met actuele gegevens. Data manipulatie moet worden gedaan buiten de RAF en de nieuwe gegevens in de objecten zijn voor de RAF terugbellen om te lezen.
    • Kan iemand mij uitleggen van de verstreken % fpsInterval deel ? Waarom moeten we “ook aanpassen voor uw opgegeven fpsInterval niet zijnde een veelvoud van RAF interval (16.7 ms)” ?
    • Alles wat je kunt regelen is wanneer u van plan bent om het overslaan van een frame. Een 60 fps monitor trekt altijd aan 16ms intervallen. Bijvoorbeeld als u wilt dat uw spel uitvoeren op 50fps, u wilt overslaan elke 6e frame. U controleert als 20ms (1000/50) is verstreken, en het is niet (alleen 16ms is verstreken), zodat u het overslaan van een frame, dan is het volgende frame 32ms is verstreken sinds u de aandacht, dus u tekent en reset. Maar dan moet je overslaan helft van de frames en draaien op 30 fps. Dus wanneer u de reset je nog wat je wachtte 12 ms te lang voor de laatste keer. Dus de volgende frame andere 16ms gaat, maar je telt als 16+12=28ms, zodat je opnieuw een kaart trekken en je wachtte 8ms te lang
    • Dit antwoord niet werken voor veel fps waarden die ik probeerde, 5, 10, 20, 30…
    • moet niet requestAnimationFrame(animate); worden na de if (elapsed > fpsInterval) {} blok ?
    • nee, dat maakt eigenlijk niet uit. RAF vertelt de browser om te bellen animate() voordat u het tekent het volgende frame. Het enige verschil in zetten, daarna is dat je zou kunnen missen een RAF bellen want je code tussen duurde langer dan de browsers (monitoren) framesnelheid. zie ook: stackoverflow.com/questions/29181253/…

  2. 37

    Update 2016/6

    Het probleem smoren de frame rate is dat het scherm heeft een constante update beoordelen, doorgaans 60 FPS.

    Als we willen 24 FPS we nooit de echte 24 fps op het scherm, we kunnen het maar niet laten zien, omdat de monitor alleen gesynchroniseerd frames op 15 fps, 30 fps of 60 fps (sommige beeldschermen ook 120 fps).

    Echter voor timing doeleinden die we kunnen berekenen en update als mogelijk.

    U kunt bouwen alle logica voor het regelen van de frame-rate door het inkapselen van berekeningen en callbacks in een object:

    function FpsCtrl(fps, callback) {
    
        var delay = 1000 / fps,                               //calc. time per frame
            time = null,                                      //start time
            frame = -1,                                       //frame count
            tref;                                             //rAF time reference
    
        function loop(timestamp) {
            if (time === null) time = timestamp;              //init start time
            var seg = Math.floor((timestamp - time) / delay); //calc frame no.
            if (seg > frame) {                                //moved to next frame?
                frame = seg;                                  //update
                callback({                                    //callback function
                    time: timestamp,
                    frame: frame
                })
            }
            tref = requestAnimationFrame(loop)
        }
    }

    Voeg dan wat controller en configuratie code:

    //play status
    this.isPlaying = false;
    
    //set frame-rate
    this.frameRate = function(newfps) {
        if (!arguments.length) return fps;
        fps = newfps;
        delay = 1000 / fps;
        frame = -1;
        time = null;
    };
    
    //enable starting/pausing of the object
    this.start = function() {
        if (!this.isPlaying) {
            this.isPlaying = true;
            tref = requestAnimationFrame(loop);
        }
    };
    
    this.pause = function() {
        if (this.isPlaying) {
            cancelAnimationFrame(tref);
            this.isPlaying = false;
            time = null;
            frame = -1;
        }
    };

    Gebruik

    Wordt het zeer eenvoudig – nu, alles wat we hebben te doen is om een instantie te maken door het instellen van callback-functie en gewenste frame rate, net als dit:

    var fc = new FpsCtrl(24, function(e) {
         //render each frame here
      });

    Start (die kan worden het standaard gedrag als gewenst):

    fc.start();

    Dat is het, alle logica is intern afgehandeld.

    Demo

    JS:

    var ctx = c.getContext("2d"), pTime = 0, mTime = 0, x = 0;
    ctx.font = "20px sans-serif";
    
    //update canvas with some information and animation
    var fps = new FpsCtrl(12, function(e) {
    	ctx.clearRect(0, 0, c.width, c.height);
    	ctx.fillText("FPS: " + fps.frameRate() + 
                     " Frame: " + e.frame + 
                     " Time: " + (e.time - pTime).toFixed(1), 4, 30);
    	pTime = e.time;
    	var x = (pTime - mTime) * 0.1;
    	if (x > c.width) mTime = pTime;
    	ctx.fillRect(x, 50, 10, 10)
    })
    
    //start the loop
    fps.start();
    
    //UI
    bState.onclick = function() {
    	fps.isPlaying ? fps.pause() : fps.start();
    };
    
    sFPS.onchange = function() {
    	fps.frameRate(+this.value)
    };
    
    function FpsCtrl(fps, callback) {
    
    	var	delay = 1000 / fps,
    		time = null,
    		frame = -1,
    		tref;
    
    	function loop(timestamp) {
    		if (time === null) time = timestamp;
    		var seg = Math.floor((timestamp - time) / delay);
    		if (seg > frame) {
    			frame = seg;
    			callback({
    				time: timestamp,
    				frame: frame
    			})
    		}
    		tref = requestAnimationFrame(loop)
    	}
    
    	this.isPlaying = false;
    	
    	this.frameRate = function(newfps) {
    		if (!arguments.length) return fps;
    		fps = newfps;
    		delay = 1000 / fps;
    		frame = -1;
    		time = null;
    	};
    	
    	this.start = function() {
    		if (!this.isPlaying) {
    			this.isPlaying = true;
    			tref = requestAnimationFrame(loop);
    		}
    	};
    	
    	this.pause = function() {
    		if (this.isPlaying) {
    			cancelAnimationFrame(tref);
    			this.isPlaying = false;
    			time = null;
    			frame = -1;
    		}
    	};
    }

    CSS:

    body {font:16px sans-serif}

    HTML:

    <label>Framerate: <select id=sFPS>
    	<option>12</option>
    	<option>15</option>
    	<option>24</option>
    	<option>25</option>
    	<option>29.97</option>
    	<option>30</option>
    	<option>60</option>
    </select></label><br>
    <canvas id=c height=60></canvas><br>
    <button id=bState>Start/Stop</button>

    Oude antwoord

    Het belangrijkste doel van requestAnimationFrame is te synchroniseren updates van de monitor refresh rate. Dit zal vereisen dat u te animeren op de FPS van de monitor of een factor (dat wil zeggen. 60, 30, 15 FPS voor een typische refresh rate @ 60 Hz).

    Als u wilt dat een meer willekeurige FPS dan is er geen punt met behulp van de rAF als de frame rate zal nooit overeenkomen met de monitor update frequentie anyways (net een frame hier en daar), die gewoon niet kan geven u een vloeiende animatie (zoals met alle frame-re-timings) en je kunt net zo goed gebruik setTimeout of setInterval plaats.

    Dit is ook een bekend probleem in de professionele video-industrie wanneer u wilt afspelen van een video op een andere FPS dan het apparaat toont vernieuwd op. Veel technieken gebruikt, zoals frame blending en complexe re-timing het opnieuw opbouwen van de tussenliggende frames op basis van bewegingsvectoren, maar met canvas deze technieken niet beschikbaar zijn en het resultaat zal altijd schokkerige video.

    var FPS = 24;  ///"silver screen"
    var isPlaying = true;
    
    function loop() {
        if (isPlaying) setTimeout(loop, 1000 / FPS);
    
        ... code for frame here
    }

    De reden waarom we setTimeout eerste (en waarom sommige plaatsen rAF eerste wanneer een poly-fill wordt gebruikt) is dat deze zal worden nauwkeuriger naarmate de setTimeout zullen in de rij een gebeurtenis onmiddellijk wanneer de lus begint, dus dat maakt niet uit hoeveel tijd het resterende code gebruiken (mits deze niet hoger is dan de time-out interval) de volgende oproep zal worden op het interval vertegenwoordigt (voor pure rAF dit is niet noodzakelijk als rAF zal proberen om te springen naar het volgende frame in ieder geval).

    Ook de moeite waard op te merken dat het plaatsen van het eerste zal ook het risico op het oproepen stapelen als met setInterval. setInterval mogelijk iets nauwkeuriger zijn voor dit gebruik.

    En kunt u gebruik maken van setInterval in plaats buiten de lus om hetzelfde te doen.

    var FPS = 29.97;   ///NTSC
    var rememberMe = setInterval(loop, 1000 / FPS);
    
    function loop() {
    
        ... code for frame here
    }

    En om te stoppen met de lus:

    clearInterval(rememberMe);

    Ter vermindering van de frame rate tijdens het tabblad wordt onscherp is, kunt u het toevoegen van een factor, zoals deze:

    var isFocus = 1;
    var FPS = 25;
    
    function loop() {
        setTimeout(loop, 1000 / (isFocus * FPS)); ///note the change here
    
        ... code for frame here
    }
    
    window.onblur = function() {
        isFocus = 0.5; ///reduce FPS to half   
    }
    
    window.onfocus = function() {
        isFocus = 1; ///full FPS
    }

    Deze manier kunt u verminderen de FPS naar 1/4 enz.

    • In sommige gevallen kunt u niet proberen te overeenkomen met het monitoren frame rate, maar eerder in beeld reeksen bijvoorbeeld drop frames. Uitstekende uitleg btw
    • Een van de grootste redenen om gas met requestAnimationFrame zou worden om line-up van de uitvoering van een bepaalde code met de browsers van de animatie frame. Dingen uiteindelijk draait een stuk soepeler lopen, vooral als je het uitvoeren van bepaalde logica op gegevens van elk frame, net als met muziek visualizers bijvoorbeeld.
    • Dit is slecht omdat het belangrijkste gebruik van requestAnimationFrame te synchroniseren DOM activiteiten (lezen/schrijven) zodat het niet gebruikt zal het pijn de prestaties bij het openen van de DOM, omdat operaties niet in de wachtrij om te worden uitgevoerd bij elkaar en kracht lay-out bijgewerkt onnodig.
    • Er is geen risico van het “oproepen stapelen”, als JavaScript wordt uitgevoerd single threaded, en geen time-out gebeurtenis wordt geactiveerd terwijl de code wordt uitgevoerd. Dus als de functie duurt langer dan de time-out, het draait gewoon bijna elke keer zo snel als het kan, terwijl de browser zou nog wel opnieuw worden getekend en leiden tot andere time-outs tussen de gesprekken.
    • Ik weet dat u de pagina te vernieuwen, kan niet worden bijgewerkt sneller dan de fps limiet op het display. Echter, is het mogelijk om te vernieuwen sneller door het activeren van een pagina opnieuw plaatsen? Omgekeerd, is het mogelijk om niet merken meerdere pagina opnieuw wordt geplaatst als ze klaar zijn sneller dan de autochtone fps rate?
  3. 29

    Ik stel voor het verpakken van uw oproep om requestAnimationFrame in een setTimeout. Als u belt setTimeout vanuit de functie van waaruit u verzocht de animatie frame, bent u verslaan het doel van requestAnimationFrame. Maar als u belt requestAnimationFrame van binnen setTimeout het werkt soepel:

    var fps = 25
    function animate() {
      setTimeout(function() {
        requestAnimationFrame(animate);
      }, 1000 / fps);
    }
    • Dit lijkt eigenlijk aan het werk in het bijhouden van de framerate omlaag en dus niet koken mijn CPU. En het is zo simpel. Proost!
    • Dit is een mooie, eenvoudige manier om het te doen voor lichtgewicht animaties. Het is een beetje out of sync hoewel, ten minste op sommige apparaten. Ik gebruikte deze techniek op een van mijn vroegere motoren. Het werkte goed tot dingen heb complex. Grootste probleem was toen verslaafd oriëntatie sensoren, het zou ofwel lag achter of onder spanning staat. Later heb ik gevonden met behulp van een aparte setInterval en communiceren updates tussen de sensoren, setInterval frames en RAF frames via object-eigenschappen mogen de sensoren en RAF gaan real-time, terwijl een animatie kan worden geregeld via eigendom updates van setInterval.
    • Beste antwoord ! Bedankt 😉
    • Mijn monitor is 60 FPS, als ik de set var fps=60, krijg ik alleen over 50 FPS met behulp van deze code. Ik wil langzaam tot 60 omdat sommige mensen hebben 120 FPS monitoren, maar ik wil niet van invloed op iedereen. Dit is verrassend moeilijk.
    • Als ik de set var fps=50, ik krijg 43 FPS. jsfiddle.net/2ycwvtpe/2
    • De reden waarom krijgt u lagere FPS dan verwacht is omdat setTimeout kunnen uitvoeren van de callback na meer dan de opgegeven vertraging. Er is een aantal mogelijke reden voor deze. En elke loop het neemt de tijd om een nieuwe timer en uitvoeren van sommige code voor het instellen van de nieuwe time-out. Je hebt geen manier om accuraat te zijn met deze, moet u altijd rekening met een trager dan verwachte resultaat, maar zolang je niet weet hoeveel langzamer het zal worden, probeert te lager de vertraging zou onjuist zijn als goed. JS in browsers is niet bedoeld om te worden zo nauwkeurig.

  4. 10

    Dit zijn allemaal goede ideeën in theorie, totdat je gaat diep. Het probleem is dat je niet kunt gashendel een RAF zonder de-synchroniseren, het verslaan van het doel voor het bestaande. Dus laat je het draaien op volle toeren, en het bijwerken van uw gegevens in een aparte lus, of zelfs een aparte thread!

    Ja, ik heb het gezegd. U kan doen multi-threaded JavaScript in de browser!

    Er zijn twee methoden ik weet dat zeer goed werken zonder jank, met veel minder geld en maken minder warmte. Nauwkeurige menselijke schaal timing en de efficiëntie van de machine zijn de netto resultaat.

    Excuses als dit een beetje omslachtig, maar hier gaat…


    Methode 1: het Bijwerken van gegevens via setInterval, en afbeeldingen via RAF.

    Gebruik maken van een aparte setInterval voor het bijwerken van de vertaling en de rotatie waarden, natuurkunde, botsingen, etc. Om die waarden in een object voor elke geanimeerde element. Het toewijzen van de transformatie string met een variabele in het object met elk setInterval ‘frame’. Houd deze objecten in een array. Stel je de interval van de gewenste fps in ms: ms=(1000/fps). Dit houdt een constante klok die het mogelijk maakt dezelfde fps op elk toestel, onafhankelijk van de snelheid. Niet toewijzen van de transformaties naar de elementen hier!

    In een requestAnimationFrame lus doorlopen uw array met een old-school voor lus-gebruik niet de nieuwere vormen hier, ze zijn traag!

    for(var i=0; i<sprite.length-1; i++){  rafUpdate(sprite[i]);  }

    In uw rafUpdate functie, voor de transformatie string van uw js object in de matrix, en de elementen-id. U moet al uw ‘sprite’ elementen bevestigd aan een variabele of gemakkelijk toegankelijk via andere middelen, zodat u geen tijd verliest ‘get’-ing hen in de RAF. Houden ze in een object met de naam na hun html-id ‘ s werkt erg goed. Stel dat een deel tot nog voordat het gaat in je SI of RAF.

    Gebruik van de RAF voor het bijwerken van uw transformeert alleen, gebruik alleen 3D-transformaties (zelfs voor 2d), en stel css “zal-wijziging: transformatie;” op elementen die zullen veranderen. Dit houdt uw transformeert gesynchroniseerd naar de native vernieuwingsfrequentie zo veel mogelijk trappen in de GPU, en vertelt de browser waar te concentreren meest.

    Dus je moet iets als dit pseudocode…

    //refs to elements to be transformed, kept in an array
    var element = [
       mario: document.getElementById('mario'),
       luigi: document.getElementById('luigi')
       //...etc.
    ]
    
    var sprite = [  //read/write this with SI.  read-only from RAF
       mario: { id: mario  ....physics data, id, and updated transform string (from SI) here  },
       luigi: {  id: luigi  .....same  }
       //...and so forth
    ] //also kept in an array (for efficient iteration)
    
    //update one sprite js object
    //data manipulation, CPU tasks for each sprite object
    //(physics, collisions, and transform-string updates here.)
    //pass the object (by reference).
    var SIupdate = function(object){
      //get pos/rot and update with movement
      object.pos.x += object.mov.pos.x;  //example, motion along x axis
      //and so on for y and z movement
      //and xyz rotational motion, scripted scaling etc
    
      //build transform string ie
      object.transform =
       'translate3d('+
         object.pos.x+','+
         object.pos.y+','+
         object.pos.z+
       ') '+
    
       //assign rotations, order depends on purpose and set-up. 
       'rotationZ('+object.rot.z+') '+
       'rotationY('+object.rot.y+') '+
       'rotationX('+object.rot.x+') '+
    
       'scale3d('.... if desired
      ;  //...etc.  include 
    }
    
    
    var fps = 30; //desired controlled frame-rate
    
    
    //CPU TASKS - SI psuedo-frame data manipulation
    setInterval(function(){
      //update each objects data
      for(var i=0; i<sprite.length-1; i++){  SIupdate(sprite[i]);  }
    },1000/fps); // note ms = 1000/fps
    
    
    //GPU TASKS - RAF callback, real frame graphics updates only
    var rAf = function(){
      //update each objects graphics
      for(var i=0; i<sprite.length-1; i++){  rAF.update(sprite[i])  }
      window.requestAnimationFrame(rAF); //loop
    }
    
    //assign new transform to sprite's element, only if it's transform has changed.
    rAF.update = function(object){     
      if(object.old_transform !== object.transform){
        element[object.id].style.transform = transform;
        object.old_transform = object.transform;
      }
    } 
    
    window.requestAnimationFrame(rAF); //begin RAF

    Hierdoor blijven de updates van de data objecten en transformeren snaren gesynchroniseerd naar de gewenste ‘frame’ tarief in de SI en de werkelijke transformatie opdrachten in de RAF gesynchroniseerd met GPU-refresh rate. Dus de werkelijke grafische updates zijn alleen in de RAF, maar de wijzigingen in de gegevens, en de bouw van het transformeren string in de SI, dus geen jankies maar ‘tijd’ stromen op het gewenste frame-rate.


    Flow:

    [setup js sprite objects and html element object references]
    
    [setup RAF and SI single-object update functions]
    
    [start SI at percieved/ideal frame-rate]
      [iterate through js objects, update data transform string for each]
      [loop back to SI]
    
    [start RAF loop]
      [iterate through js objects, read object's transform string and assign it to it's html element]
      [loop back to RAF]

    Methode 2. Zet de SI in een web-werknemer. Dit is FAAAST en glad!

    Hetzelfde als methode 1, maar zet de SI-web-werknemer. Het zal worden uitgevoerd op een volledig aparte thread dan, het verlaten van de pagina alleen te maken met de RAF en de UI. Pas de sprite array heen en weer als een ‘overdraagbare object’. Dit is buko snel. Het is niet de tijd nemen om te klonen of serialiseren, maar het is niet zo dat doorgeven door verwijzing in dat de verwijzing van de andere kant is vernietigd, dus je moet aan beide zijden worden voorbij naar de andere kant, en alleen bijwerken wanneer aanwezig, een soort van als het passeren van een opmerking over en weer met je vriendin op de middelbare school.

    Slechts één kan lezen en schrijven tegelijk. Dit is prima zo lang ze controleren als het niet undefined om te voorkomen dat een fout. De RAF is SNEL en kick het onmiddellijk terug, dan gaan door een bos van GPU frames alleen controleren als het is verzonden nog niet. De SI in de web-werknemer zal de sprite array meeste van de tijd, en zal een update van hun positie, beweging en natuurkunde gegevens, alsmede het creëren van de nieuwe transformeren string, en dan weer terug te sturen naar de RAF in de pagina.

    Dit is de snelste manier die ik ken om te animeren elementen via een script. De twee functies worden uitgevoerd als twee afzonderlijke programma ‘s, op twee aparte draden, gebruik te maken van multi-core CPU’ s op een manier die een enkele js script niet. Multi-threaded javascript animatie.

    En zal dat soepel en zonder jank, maar op de werkelijke opgegeven frame-rate, met zeer weinig verschil.


    Resultaat:

    Een van deze twee methoden zal ervoor zorgen dat uw script draait op dezelfde snelheid op elke PC, telefoon, tablet, etc (binnen de mogelijkheden van het apparaat en van de browser, natuurlijk).

    • Als een side-note-in Methode 1, als er te veel activiteit in uw setInterval kan vertragen uw RAF door single-threaded async. U kunt het verzachten van dit doorbreken van die activiteit over meer dan SI frame, zodat async zal de besturing terug naar RAF sneller. Vergeet niet, RAF gaat bij max frame-rate, maar synchroniseert grafische veranderingen met het scherm, dus het is ok om het overslaan van een paar RAF frames– zolang u niet overslaan meer dan SI frames het is niet jank.
    • Methode 2 is robuuster, want het is echt multi-tasking de twee lussen, niet het heen en weer door async, maar u wilt nog steeds om te voorkomen dat uw SI-frame langer duurt dan uw gewenste frame-rate, dus het splitsen van SI activiteit nog wenselijk zijn als er een heleboel data-manipulatie gaande dat zou nog meer dan een SI frame in te vullen.
    • Ik dacht dat het de moeite waard te vermelden, als een opmerking van belang, dat het uitvoeren van gepaarde loops zoals dit eigenlijk de registers in Chroom DevTools dat de GPU draait op de frame-rate gespecificeerd in de setInterval loop! Het verschijnt alleen RAF frames in welke grafische veranderingen optreden, worden geteld als de frames door de FOD meter. Zo RAF frames waarin alleen niet-grafische werk, of zelfs alleen maar lege loops, tellen niet zo ver als de GPU is betrokken. Ik vind dit interessant, omdat een uitgangspunt voor verder onderzoek.
    • Ik geloof dat dit de oplossing is het probleem dat deze blijft draaien wanneer rAF onderbroken wordt, bijvoorbeeld omdat de gebruiker overgestapt naar een ander tabblad.
    • P. S. ik heb wat te lezen en het lijkt de meeste browsers beperken tijdgebonden afspraken te keer per seconde op de achtergrond tabbladen hoe dan ook (dat moet waarschijnlijk ook behandeld worden op een bepaalde manier). Als u nog wilt om het probleem te verhelpen en volledig te onderbreken wanneer het niet zichtbaar is, lijkt er de visibilitychange evenement.
    • Leuk! Ja, ze doen het beperken van de tabs in BG voor goede redenen! RAF eigenlijk gewoon stopt. Wist niet over visibilitychange evenement. Dat maakt het veel eenvoudiger om te onderbreken en/of te berekenen waar de zaken moeten worden zodra de tab is weer actief!

  5. 3

    Hoe gemakkelijk de gashendel op een specifieke FPS:

    //timestamps are ms passed since document creation.
    //lastTimestamp can be initialized to 0, if main loop is executed immediately
    var lastTimestamp = 0,
        maxFPS = 30,
        timestep = 1000 / maxFPS; //ms for each frame
    
    function main(timestamp) {
        window.requestAnimationFrame(main);
    
        //skip if timestep ms hasn't passed since last frame
        if (timestamp - lastTimestamp < timestep) return;
    
        lastTimestamp = timestamp;
    
        //draw frame here
    }
    
    window.requestAnimationFrame(main);

    Bron: Een Gedetailleerde Uitleg van JavaScript Spel Lussen en de Timing van Isaac Sukin

    • Als mijn monitor draait op 60 FPS en ik wil mijn spel uitvoeren op 58 FPS ik maxFPS=58, deze zal draaien op 30 FPS, want het zal overslaan elke 2de frame.
    • Ja, ik heb dit geprobeerd een zo goed. Ik kies niet om daadwerkelijk het afremmen van de RAF zelf– alleen de wijzigingen worden bijgewerkt door de setTimeout. In Chrome tenminste, dit zorgt ervoor dat de effectieve fps te draaien op de setTimeouts tempo, volgens metingen in DevTools. Natuurlijk kan het alleen update real video beelden op de snelheid van de video kaart en monitor refresh rate, maar deze methode lijkt te werken met de minste jankies, dus soepelste “schijnbare” fps controle, dat is wat ik ga voor.
    • Sinds ik met het bijhouden van alle beweging in JS voorwerpen afzonderlijk van de RAF, dit houdt de animatie logica, collision detection of wat u ook nodig heeft, draait op een perceptueel consistente tarief, ongeacht de RAF of de setTimeout, met een beetje extra wiskunde.
  6. 2

    Overslaan requestAnimationFrame oorzaak niet glad(gewenste) animatie op maat fps.

    JS:

    //Input/output DOM elements
    var $results = $("#results");
    var $fps = $("#fps");
    var $period = $("#period");
    
    //Array of FPS samples for graphing
    
    //Animation state/parameters
    var fpsInterval, lastDrawTime, frameCount_timed, frameCount, lastSampleTime, 
    		currentFps=0, currentFps_timed=0;
    var intervalID, requestID;
    
    //Setup canvas being animated
    var canvas = document.getElementById("c");
    var canvas_timed = document.getElementById("c2");
    canvas_timed.width = canvas.width = 300;
    canvas_timed.height = canvas.height = 300;
    var ctx = canvas.getContext("2d");
    var ctx2 = canvas_timed.getContext("2d");
    
    
    //Setup input event handlers
    
    $fps.on('click change keyup', function() {
        if (this.value > 0) {
            fpsInterval = 1000 / +this.value;
        }
    });
    
    $period.on('click change keyup', function() {
        if (this.value > 0) {
            if (intervalID) {
                clearInterval(intervalID);
            }
            intervalID = setInterval(sampleFps, +this.value);
        }
    });
    
    
    function startAnimating(fps, sampleFreq) {
    
        ctx.fillStyle = ctx2.fillStyle = "#000";
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx2.fillRect(0, 0, canvas.width, canvas.height);
        ctx2.font = ctx.font = "32px sans";
        
        fpsInterval = 1000 / fps;
        lastDrawTime = performance.now();
        lastSampleTime = lastDrawTime;
        frameCount = 0;
        frameCount_timed = 0;
        animate();
        
        intervalID = setInterval(sampleFps, sampleFreq);
    		animate_timed()
    }
    
    function sampleFps() {
        //sample FPS
        var now = performance.now();
        if (frameCount > 0) {
            currentFps =
                (frameCount / (now - lastSampleTime) * 1000).toFixed(2);
            currentFps_timed =
                (frameCount_timed / (now - lastSampleTime) * 1000).toFixed(2);
            $results.text(currentFps + " | " + currentFps_timed);
            
            frameCount = 0;
            frameCount_timed = 0;
        }
        lastSampleTime = now;
    }
    
    function drawNextFrame(now, canvas, ctx, fpsCount) {
        //Just draw an oscillating seconds-hand
        
        var length = Math.min(canvas.width, canvas.height) / 2.1;
        var step = 15000;
        var theta = (now % step) / step * 2 * Math.PI;
    
        var xCenter = canvas.width / 2;
        var yCenter = canvas.height / 2;
        
        var x = xCenter + length * Math.cos(theta);
        var y = yCenter + length * Math.sin(theta);
        
        ctx.beginPath();
        ctx.moveTo(xCenter, yCenter);
        ctx.lineTo(x, y);
      	ctx.fillStyle = ctx.strokeStyle = 'white';
        ctx.stroke();
        
        var theta2 = theta + 3.14/6;
        
        ctx.beginPath();
        ctx.moveTo(xCenter, yCenter);
        ctx.lineTo(x, y);
        ctx.arc(xCenter, yCenter, length*2, theta, theta2);
    
        ctx.fillStyle = "rgba(0,0,0,.1)"
        ctx.fill();
        
        ctx.fillStyle = "#000";
        ctx.fillRect(0,0,100,30);
        
        ctx.fillStyle = "#080";
        ctx.fillText(fpsCount,10,30);
    }
    
    //redraw second canvas each fpsInterval (1000/fps)
    function animate_timed() {
        frameCount_timed++;
        drawNextFrame( performance.now(), canvas_timed, ctx2, currentFps_timed);
        
        setTimeout(animate_timed, fpsInterval);
    }
    
    function animate(now) {
        //request another frame
        requestAnimationFrame(animate);
        
        //calc elapsed time since last loop
        var elapsed = now - lastDrawTime;
    
        //if enough time has elapsed, draw the next frame
        if (elapsed > fpsInterval) {
            //Get ready for next frame by setting lastDrawTime=now, but...
            //Also, adjust for fpsInterval not being multiple of 16.67
            lastDrawTime = now - (elapsed % fpsInterval);
    
            frameCount++;
        		drawNextFrame(now, canvas, ctx, currentFps);
        }
    }
    startAnimating(+$fps.val(), +$period.val());

    CSS:

    input{
      width:100px;
    }
    #tvs{
      color:red;
      padding:0px 25px;
    }
    H3{
      font-weight:400;
    }

    HTML:

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <h3>requestAnimationFrame skipping <span id="tvs">vs.</span> setTimeout() redraw</h3>
    <div>
        <input id="fps" type="number" value="33"/> FPS:
        <span id="results"></span>
    </div>
    <div>
        <input id="period" type="number" value="1000"/> Sample period (fps, ms)
    </div>
    <canvas id="c"></canvas><canvas id="c2"></canvas>

    Originele code door @tavnab.

  7. 1

    Ik doe het altijd op deze zeer eenvoudige manier zonder knoeien met tijdsregistratie:

    var fps, eachNthFrame, frameCount;
    
    fps = 30;
    
    //This variable specifies how many frames should be skipped.
    //If it is 1 then no frames are skipped. If it is 2, one frame 
    //is skipped so "eachSecondFrame" is renderd.
    eachNthFrame = Math.round((1000 / fps) / 16.66);
    
    //This variable is the number of the current frame. It is set to eachNthFrame so that the 
    //first frame will be renderd.
    frameCount = eachNthFrame;
    
    requestAnimationFrame(frame);
    
    //I think the rest is self-explanatory
    fucntion frame() {
      if (frameCount == eachNthFrame) {
        frameCount = 0;
        animate();
      }
      frameCount++;
      requestAnimationFrame(frame);
    }
    • Dit loopt te snel als uw scherm 120 fps.
  8. 0

    Hier is een goede uitleg die ik vond: CreativeJS.com, doorloopt een setTimeou) oproep in de functie doorgegeven aan requestAnimationFrame. Mijn zorg met een “normaal” requestionAnimationFrame zou worden, “wat als ik alleen wilt het animeren drie keer een tweede?” Zelfs met requestAnimationFrame (in tegenstelling tot setTimeout) is dat het nog afval (sommige) bedrag van de “energie” (wat betekent dat de Browser de code is iets te doen, en eventueel het vertragen van het systeem naar beneden) 60 of 120 of hoeveel keer per seconde, in tegenstelling tot slechts twee of drie keer een tweede (als je zou willen).

    De meeste van de tijd draai ik mijn browsers met JavaScript intentially uit voor reden dan ook. Maar, ik ben met behulp van Yosemite 10.10.3, en ik denk dat er een soort van timer probleem met het – in ieder geval op mijn oude systeem (relatief oude betekenis 2011).

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *