अनुरोध के साथ एफपीएस को नियंत्रित करना।


140

ऐसा लगता requestAnimationFrameहै कि अब चीजों को चेतन करने के लिए वास्तविक तरीका है। यह मेरे लिए सबसे अधिक भाग के लिए बहुत अच्छा काम करता है, लेकिन अभी मैं कुछ कैनवास एनिमेशन करने की कोशिश कर रहा हूं और मैं सोच रहा था: क्या यह सुनिश्चित करने का कोई तरीका है कि यह एक निश्चित एफपीएस पर चलता है? मैं समझता हूं कि rAF का उद्देश्य लगातार सुचारू एनिमेशन के लिए है, और मैं अपने एनीमेशन को तड़का बनाने का जोखिम उठा सकता हूं, लेकिन अभी यह बहुत ही मनमाने ढंग से अलग-अलग गति से चलता है, और मैं सोच रहा हूं कि क्या मुकाबला करने का कोई तरीका है किसी तरह।

मैं उपयोग करूँगा, setIntervalलेकिन मैं वह अनुकूलन चाहता हूँ जो rAF प्रदान करता है (विशेषकर स्वचालित रूप से तब रुकता है जब टैब फोकस में होता है)।

यदि कोई मेरे कोड को देखना चाहता है, तो यह बहुत अधिक है:

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

जहां Node.drawFlash () सिर्फ कुछ कोड है जो काउंटर वेरिएबल के आधार पर त्रिज्या निर्धारित करता है और फिर एक वृत्त खींचता है।


1
क्या आपका एनिमेशन पिछड़ता है? मुझे लगता है कि एक सबसे बड़ा फायदा requestAnimationFrame(जैसा कि नाम का सुझाव है) केवल एक एनीमेशन फ्रेम का अनुरोध करने के लिए जब यह आवश्यक हो। मान लीजिए कि आप एक स्थिर ब्लैक कैनवास दिखाते हैं, आपको 0 एफपीएस मिलना चाहिए क्योंकि किसी नए फ्रेम की आवश्यकता नहीं है। लेकिन यदि आप एक ऐसा एनीमेशन प्रदर्शित कर रहे हैं जिसकी आवश्यकता 60fps है, तो आपको वह भी प्राप्त करना चाहिए। rAFबस बेकार तख्ते को "छोड़ना" और फिर सीपीयू को बचाने की अनुमति देता है।
अधिकतम

setInterval निष्क्रिय टैब में भी काम नहीं करता है।
विलिअसएल

यह कोड 90hz डिस्प्ले बनाम 60hz डिस्प्ले बनाम 144hz डिस्प्ले पर अलग तरह से चलता है।
मैनथ्रैक्स

जवाबों:


190

किसी थ्रॉटल अनुरोध को कैसे हटाएं

5 FPS पर डेमो थ्रॉटलिंग: http://jsfiddle.net/m1erickson/CtsY3/

यह विधि अंतिम फ़्रेम लूप को निष्पादित करने के बाद बीता हुआ समय का परीक्षण करके काम करती है।

आपका ड्रॉइंग कोड तभी निष्पादित होता है जब आपका निर्दिष्ट एफपीएस अंतराल समाप्त हो गया हो।

कोड का पहला भाग बीते समय की गणना के लिए उपयोग किए जाने वाले कुछ चर सेट करता है।

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

और यह कोड वास्तविक रिक्वेस्टएनिमेशनफ्रेम लूप है जो आपके निर्दिष्ट एफपीएस पर आकर्षित होता है।

// 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

    }
}

5
उत्कृष्ट व्याख्या और उदाहरण। इसे स्वीकृत उत्तर के रूप में चिह्नित किया जाना चाहिए
muxcmux

13
अच्छा डेमो - यह स्वीकार किया जाना चाहिए। यहाँ, अपने फिडेल को फोर्क किया, Date.now () के बजाय window.performance.now () का उपयोग करके प्रदर्शित करने के लिए। यह उच्च रिज़ॉल्यूशन टाइमस्टैम्प कि आरएएफ पहले से ही प्राप्त करता है के साथ अच्छी तरह से चला जाता है, इसलिए वहाँ कॉलबैक अंदर Date.now () कॉल करने के लिए कोई ज़रूरत नहीं है: jsfiddle.net/chicagogrooves/nRpVD/2
डीन रैडक्लिफ

2
नई rAF टाइमस्टैम्प सुविधा का उपयोग करके अद्यतन लिंक के लिए धन्यवाद। नया आरएएफ टाइमस्टैम्प उपयोगी घुसपैठ को जोड़ता है और यह Date.now की तुलना में अधिक सटीक है।
मार्क

13
यह वास्तव में अच्छा डेमो है, जिसने मुझे अपना खुद का ( JSFiddle ) बनाने के लिए प्रेरित किया । मुख्य अंतर तिथि के बजाय rAF (डीन के डेमो की तरह) का उपयोग कर रहे हैं, नियंत्रण को जोड़ने के लिए गतिशील रूप से लक्ष्य फ्रैमरेट को समायोजित करना, एनीमेशन से एक अलग अंतराल पर फ्रैमरेट का नमूना लेना और ऐतिहासिक फ्रैमरेट्स का एक ग्राफ जोड़ना है।
तवनाब

1
आप सभी को नियंत्रित कर सकते हैं जब आप एक फ्रेम को छोड़ने जा रहे हैं। एक 60 एफपीएस मॉनिटर हमेशा 16ms के अंतराल पर खींचता है। उदाहरण के लिए यदि आप चाहते हैं कि आपका गेम 50fps पर चले, तो आप हर 6 वें फ्रेम को छोड़ना चाहते हैं। यदि आप 20ms (1000/50) समाप्त हो गए हैं, और यह जाँच (केवल 16ms बीत चुका है) नहीं है, तो आप एक फ्रेम को छोड़ देते हैं, तो अगले फ्रेम 32ms जब आप आकर्षित किया है, तो आप आकर्षित और रीसेट। लेकिन फिर आप आधे फ्रेम को छोड़ देंगे और 30fps पर चलाएंगे। इसलिए जब आप रीसेट करते हैं तो आपको याद है कि आपने पिछली बार 12ms बहुत लंबा इंतजार किया था। तो अगली फ्रेम एक और 16ms गुजरती है, लेकिन आप इसे 16 + 12 = 28ms के रूप में गिनते हैं ताकि आप फिर से ड्रा करें और आपने 8ms बहुत लंबा इंतजार किया
कर्टिस

47

अपडेट 2016/6

फ़्रेम दर को थ्रॉटलिंग करने में समस्या यह है कि स्क्रीन में निरंतर अद्यतन दर होती है, आमतौर पर 60 एफपीएस।

अगर हम चाहते हैं कि 24 एफपीएस हमें स्क्रीन पर कभी भी सही 24 एफपीएस नहीं मिलेगा, तो हम इसे इस तरह से समय दे सकते हैं लेकिन इसे नहीं दिखा सकते हैं क्योंकि मॉनिटर केवल 15 एफपीएस, 30 एफपीएस या 60 एफपीएस पर सिंक किए गए फ़्रेम दिखा सकता है (कुछ मॉनिटर 120 एफपीएस भी। )।

हालांकि, समय के उद्देश्यों के लिए हम गणना कर सकते हैं और जब संभव हो अपडेट कर सकते हैं।

आप किसी ऑब्जेक्ट में कैलकुलेशन और कॉलबैक को इनकैप करके फ्रेम-दर को नियंत्रित करने के लिए सभी तर्क का निर्माण कर सकते हैं:

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

फिर कुछ नियंत्रक और कॉन्फ़िगरेशन कोड जोड़ें:

// 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;
    }
};

प्रयोग

यह बहुत सरल हो जाता है - अब, हमें बस इतना करना है कि कॉलबैक फ़ंक्शन और वांछित फ्रेम दर को इस तरह सेट करके एक उदाहरण बनाना है:

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

फिर शुरू करें (यदि वांछित व्यवहार हो सकता है):

fc.start();

यही है, सभी तर्क आंतरिक रूप से नियंत्रित किए जाते हैं।

डेमो

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;
		}
	};
}
body {font:16px sans-serif}
<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>

पुराना उत्तर

इसका मुख्य उद्देश्य requestAnimationFrameमॉनिटर के रिफ्रेश रेट के अपडेट को सिंक करना है। यह आपको मॉनिटर के FPS पर चेतन करने या इसके एक कारक (अर्थात 60, 30, 15 FPS के लिए एक विशिष्ट ताज़ा दर @ 60 हर्ट्ज) की आवश्यकता होगी।

यदि आप अधिक मनमाना एफपीएस चाहते हैं तो आरएएफ का उपयोग करने का कोई मतलब नहीं है क्योंकि फ्रेम दर मॉनिटर के अपडेट फ्रिक्वेंसी वैसे (बस यहां और वहां एक फ्रेम) से मेल नहीं खाएगी, जो आपको केवल एक चिकनी एनीमेशन नहीं दे सकती है (जैसा कि सभी फ्रेम री-टाइमिंग के साथ है ) और आप उपयोग setTimeoutया setIntervalइसके बजाय हो सकता है ।

यह पेशेवर वीडियो उद्योग में भी एक अच्छी तरह से ज्ञात समस्या है जब आप एक वीडियो को एक अलग एफपीएस पर प्लेबैक करना चाहते हैं तो डिवाइस इसे ताज़ा दिखाता है। कई तकनीकों का उपयोग किया गया है जैसे मोशन वैक्टर के आधार पर फ्रेम ब्लेंडिंग और कॉम्प्लेक्स री-टाइमिंग री-बिल्डिंग इंटरमीडिएट फ्रेम, लेकिन कैनवस के साथ ये तकनीक उपलब्ध नहीं हैं और परिणाम हमेशा झटकेदार वीडियो होगा।

var FPS = 24;  /// "silver screen"
var isPlaying = true;

function loop() {
    if (isPlaying) setTimeout(loop, 1000 / FPS);

    ... code for frame here
}

इसका कारण यह है कि हम setTimeout पहले स्थान पर हैं (और कुछ जगह rAFपहले जब पाली-फिल का उपयोग किया जाता है) यह है कि यह अधिक सटीक setTimeoutहोगा क्योंकि एक घटना तुरंत घट जाएगी जब लूप शुरू होता है ताकि शेष कोड का उपयोग करने में कितना समय लगे (बशर्ते कि यह टाइमआउट अंतराल से अधिक नहीं है) अगली कॉल उस अंतराल पर होगी जो इसका प्रतिनिधित्व करता है (शुद्ध आरएएफ के लिए यह आवश्यक नहीं है क्योंकि आरएएफ किसी भी मामले में अगले फ्रेम पर कूदने की कोशिश करेगा)।

यह भी ध्यान रखें कि इसे पहले रखने से कॉल स्टैकिंग भी ख़त्म हो जाएगी setIntervalsetIntervalइस उपयोग के लिए थोड़ा और सटीक हो सकता है।

और आप ऐसा setIntervalकरने के लिए लूप के बजाय उपयोग कर सकते हैं ।

var FPS = 29.97;   /// NTSC
var rememberMe = setInterval(loop, 1000 / FPS);

function loop() {

    ... code for frame here
}

और पाश को रोकने के लिए:

clearInterval(rememberMe);

फ्रेम दर को कम करने के लिए जब टैब धुंधला हो जाता है तो आप इस तरह से एक कारक जोड़ सकते हैं:

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
}

इस तरह आप FPS को 1/4 आदि तक कम कर सकते हैं।


4
कुछ उदाहरणों में आप मॉनिटर फ्रेम दर से मिलान करने की कोशिश नहीं कर रहे हैं, बल्कि उदाहरण के लिए, छवि अनुक्रमों में, फ्रेम को छोड़ दें। उत्कृष्ट स्पष्टीकरण btw
साइडलॉसन

3
RequestAnimationFrame के साथ थ्रॉटल करने का एक सबसे बड़ा कारण ब्राउज़र के एनीमेशन फ्रेम के साथ कुछ कोड का निष्पादन करना होगा। चीजें बहुत चिकनी चल रही हैं, खासकर अगर आप हर फ्रेम में डेटा पर कुछ तर्क चला रहे हैं, उदाहरण के लिए संगीत विज़ुअलाइज़र के साथ।
क्रिस डॉल्फिन

4
यह बुरा है क्योंकि इसका मुख्य उपयोग requestAnimationFrameDOM ऑपरेशंस (रीड / राइट) को सिंक्रोनाइज़ करना है इसलिए इसका उपयोग नहीं करने पर DOM को एक्सेस करते समय परफॉर्मेंस को नुकसान होगा, क्योंकि ऑपरेशंस को एक साथ निष्पादित करने के लिए कतारबद्ध नहीं किया जाएगा और लेआउट को अनावश्यक रूप से मजबूर करेगा।
vsync

1
"कॉल स्टैकिंग अप" का कोई जोखिम नहीं है, क्योंकि जावास्क्रिप्ट एकल थ्रेडेड चलता है, और जब आपका कोड चल रहा होता है तो कोई टाइमआउट इवेंट चालू नहीं होता है। इसलिए यदि फ़ंक्शन टाइमआउट की तुलना में अधिक समय लेता है, तो यह लगभग किसी भी समय जितना तेज़ हो सकता है, उतना ही चलता है, जबकि ब्राउज़र अभी भी कॉल को इनबेट करने के लिए अन्य समयबाहर को ट्रिगर और ट्रिगर करेगा।
ड्रोन

मुझे पता है कि आप पृष्ठ को ताज़ा करते हैं, डिस्प्ले पर एफपीएस सीमा से अधिक तेजी से अपडेट नहीं किया जा सकता है। हालाँकि, क्या पृष्ठ रीफ़्लो को ट्रिगर करके तेज़ी से ताज़ा करना संभव है? इसके विपरीत, क्या यह संभव है कि मूल एफपीएस दर की तुलना में तेजी से किए जाने पर कई पेजों को फिर से न देखें?
ट्रैविस जे

36

मेरा सुझाव है कि अपने कॉल को requestAnimationFrameएक में लपेटें setTimeout। यदि आप setTimeoutउस फ़ंक्शन से कॉल करते हैं जिसमें से आपने एनीमेशन फ़्रेम का अनुरोध किया है, तो आप के उद्देश्य को हरा रहे हैं requestAnimationFrame। लेकिन अगर आप इसके requestAnimationFrameभीतर से कॉल करते हैं तो setTimeoutआसानी से काम करता है:

var fps = 25
function animate() {
  setTimeout(function() {
    requestAnimationFrame(animate);
  }, 1000 / fps);
}

1
यह वास्तव में framerate नीचे रखने में काम करने लगता है और इसलिए मेरे सीपीयू को खाना पकाने नहीं। और यह बहुत आसान है। चीयर्स!
फोर्स

यह हल्के एनिमेशन के लिए एक अच्छा, सरल तरीका है। यह सिंक से थोड़ा बाहर निकलता है, हालांकि कम से कम कुछ उपकरणों पर। मैंने अपने पूर्व इंजनों में से एक पर इस तकनीक का इस्तेमाल किया। यह तब तक अच्छा काम किया जब तक चीजें जटिल नहीं हो गईं। सबसे बड़ी समस्या थी जब अभिविन्यास सेंसर तक झुका हुआ था, यह या तो पीछे हो जाएगा या उछल जाएगा। बाद में मैंने एक अलग सेटइंटरवाल का उपयोग किया और सेंसर, सेटइंटरवाल फ्रेम और आरएएफ फ्रेम के बीच ऑब्जेक्ट गुणों के माध्यम से संचार को अपडेट किया, सेंसर और आरएएफ को वास्तविक समय में जाने की अनुमति दी, जबकि एनीमेशन टाइम को सेट इन्टरवल से संपत्ति अपडेट के माध्यम से नियंत्रित किया जा सकता है।
jdmayfield

सबसे बढ़िया उत्तर ! धन्यवाद;)
538ROMEO

मेरा मॉनिटर 60 एफपीएस है, अगर मैं var एफपीएस = 60 सेट करता हूं, तो मुझे केवल इस कोड का उपयोग करके लगभग 50 एफपीएस मिलते हैं। मैं इसे 60 तक धीमा करना चाहता हूं क्योंकि कुछ लोगों के पास 120 एफपीएस मॉनिटर हैं, लेकिन मैं बाकी सभी को प्रभावित नहीं करना चाहता। यह आश्चर्यजनक रूप से कठिन है।
कर्टिस

कारण यह है कि आपको उम्मीद से कम एफपीएस मिलता है क्योंकि सेटटाइमआउट निर्दिष्ट देरी से अधिक के बाद कॉलबैक को निष्पादित कर सकता है। इसके कई संभावित कारण है। और हर लूप में एक नया टाइमर सेट करने के लिए समय लगता है और नए टाइमआउट को सेट करने से पहले कुछ कोड निष्पादित करता है। आपके पास इसके सटीक होने का कोई तरीका नहीं है, आपको हमेशा अपेक्षित परिणाम की तुलना में धीमी गति से विचार करना चाहिए, लेकिन जब तक आप यह नहीं जानते हैं कि यह कितना धीमा होगा, देरी को कम करने की कोशिश गलत भी होगी। ब्राउज़रों में JS का मतलब इतना सटीक नहीं है।
pdepmcp

17

सिद्धांत में ये सभी अच्छे विचार हैं, जब तक आप गहरे नहीं जाते। समस्या यह है कि आप इसे बिना डी-सिंक्रोनाइज़ किए बिना आरएएफ को थ्रॉटल नहीं कर सकते हैं, यह मौजूदा के लिए बहुत उद्देश्य है। तो आप इसे पूर्ण गति से चलाने के लिए, और एक अलग पाश में अपने डेटा को अपडेट करते हैं , या यहां तक कि एक अलग थ्रेड!

हाँ, मैंने कहा। आप ब्राउज़र में बहु-थ्रेडेड जावास्क्रिप्ट कर सकते हैं!

मुझे पता है कि दो तरीके हैं जो बिना अच्छी तरह से काम करते हैं, बहुत कम रस का उपयोग करते हैं और कम गर्मी पैदा करते हैं। सटीक मानव-स्केल समय और मशीन दक्षता शुद्ध परिणाम हैं।

माफ़ी अगर यह थोड़ी सी चिंताजनक है, लेकिन यहाँ जाता है ...


विधि 1: सेट इन्टरवल और आरएएफ के माध्यम से ग्राफिक्स अपडेट करें।

अनुवाद और रोटेशन मान, भौतिकी, टकराव, आदि को अद्यतन करने के लिए एक अलग सेट का उपयोग करें। प्रत्येक एनिमेटेड तत्व के लिए उन मानों को एक वस्तु में रखें। प्रत्येक सेट में एक चर में परिवर्तन स्ट्रिंग असाइन करेंइंटरवल 'फ्रेम'। इन वस्तुओं को एक सरणी में रखें। एमएस में अपने वांछित एफपीएस पर अपना अंतराल सेट करें: एमएस = (1000 / एफपीएस)। यह एक स्थिर घड़ी रखता है जो RAF गति की परवाह किए बिना किसी भी डिवाइस पर समान एफपीएस की अनुमति देता है। यहां तत्वों को रूपांतरित न करें!

एक requestAnimationFrame लूप में, लूप के लिए एक पुराने स्कूल के साथ अपने एरे के माध्यम से पुनरावृति - यहाँ नए रूपों का उपयोग न करें, वे धीमे हैं!

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

अपने rafUpdate फ़ंक्शन में, सरणी में अपनी js ऑब्जेक्ट से ट्रांसफ़ॉर्म स्ट्रिंग प्राप्त करें और इसके एलिमेंट्स id। आपके पास पहले से ही आपके '' स्प्राइट '' तत्व होने चाहिए जो चर से जुड़े हों या अन्य माध्यमों से आसानी से सुलभ हों, ताकि आप समय न गवाएं '' उन्हें आरएएफ में शामिल करें। उन्हें अपने html id के काम के नाम पर एक ऑब्जेक्ट में रखना बहुत अच्छा है। इससे पहले कि वह हिस्सा आपके SI या RAF में चला जाए, सेट करें।

अपने रूपांतरण अद्यतन करने के लिए आरएएफ का उपयोग केवल , केवल 3 डी रूपांतरण (यहां तक कि 2 डी के लिए), और सेट सीएसएस का उपयोग करें "होगा परिवर्तन: बदलना," तत्वों पर जो बदल जाएगा। यह आपके परिवर्तनों को देशी रिफ्रेश रेट के अनुसार सिंक करता है, जितना संभव हो सके, GPU में किक करता है, और ब्राउज़र को बताता है कि सबसे अधिक ध्यान कहाँ लगाना है।

तो आपके पास कुछ इस तरह का छद्मकोड होना चाहिए ...

// 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

यह डेटा ऑब्जेक्ट्स में आपके अपडेट को बनाए रखता है और एसआई में वांछित 'फ्रेम' दर के लिए सिंक किए गए स्ट्रिंग्स को बदल देता है, और आरएएफ में वास्तविक ट्रांसफॉर्म असाइनमेंट GPU रीफ्रेश रेट के लिए सिंक किया जाता है। तो वास्तविक ग्राफिक्स अपडेट केवल आरएएफ में होते हैं, लेकिन डेटा में परिवर्तन, और ट्रांसफॉर्मिंग स्ट्रिंग का निर्माण एसआई में होता है, इस प्रकार कोई भी jankies लेकिन वांछित फ्रेम-दर पर 'समय' बहती है।


बहे:

[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]

विधि 2. वेब-वर्कर में SI डालें। यह एक FAAAST और चिकनी है!

विधि 1 के समान है, लेकिन वेब-कार्यकर्ता में SI डाल दिया है। यह पूरी तरह से अलग थ्रेड पर चलेगा, पेज को केवल RAF और UI से निपटने के लिए छोड़ देगा। स्प्राइट एरे को 'ट्रांसफ़रेबल ऑब्जेक्ट' के रूप में आगे-पीछे करें। यह बुको तेजी से है। यह क्लोन या क्रमबद्ध होने में समय नहीं लेता है, लेकिन यह संदर्भ के माध्यम से गुजरने जैसा नहीं है कि दूसरी तरफ से संदर्भ नष्ट हो जाता है, इसलिए आपको दोनों पक्षों को दूसरी तरफ पास करने की आवश्यकता होगी, और वर्तमान में केवल उन्हें अपडेट करें, सॉर्ट करें हाई-स्कूल में अपनी प्रेमिका के साथ आगे और पीछे एक नोट पास करना पसंद करते हैं।

एक बार में एक ही पढ़ और लिख सकता है। यह तब तक ठीक है जब तक कि वे जाँच न कर लें कि कहीं त्रुटि तो नहीं है। आरएएफ फास्ट है और इसे तुरंत वापस लाएगा, फिर जीपीयू फ़्रेमों के एक गुच्छा से गुजरें, बस जाँच करें कि क्या इसे अभी तक वापस भेजा गया है। वेब-वर्कर में SI के पास ज्यादातर समय स्प्राइट एरे होगा, और पोजिशन, मूवमेंट और फिजिक्स डेटा को अपडेट करेगा, साथ ही नए ट्रांसफॉर्मेशन स्ट्रिंग को बनाएगा, फिर इसे पेज में RAF में वापस कर देगा।

यह स्क्रिप्ट के माध्यम से तत्वों को जानने का सबसे तेज़ तरीका है। दो कार्य दो अलग-अलग कार्यक्रमों के रूप में चल रहे हैं, दो अलग-अलग थ्रेड्स पर, मल्टी-कोर सीपीयू का लाभ इस तरह से लिया जा सकता है कि एक एकल जेएस स्क्रिप्ट नहीं करता है। मल्टी-थ्रेडेड जावास्क्रिप्ट एनीमेशन।

और यह बिना जंक के इतनी आसानी से करेगा, लेकिन वास्तविक निर्दिष्ट फ्रेम-दर पर, बहुत कम विचलन के साथ।


परिणाम:

या तो इन दो तरीकों से यह सुनिश्चित होगा कि आपकी स्क्रिप्ट किसी भी पीसी, फोन, टैबलेट आदि पर समान गति से चलेगी (डिवाइस और ब्राउज़र की क्षमताओं के भीतर, बिल्कुल)।


एक साइड नोट के रूप में-- विधि 1 में, यदि आपके सेटअंटरवल में बहुत अधिक गतिविधि है, तो यह एकल-थ्रेडेड एस्किंस के कारण आपके आरएएफ को धीमा कर सकता है। आप इस गतिविधि को एसआई फ्रेम पर अधिक से अधिक ब्रेकिंग को कम कर सकते हैं, इसलिए async आरएएफ क्विकर को वापस नियंत्रित करेगा। याद रखें, आरएएफ अधिकतम फ्रेम-दर पर जाता है, लेकिन प्रदर्शन के साथ चित्रमय परिवर्तनों को सिंक करता है, इसलिए कुछ आरएएफ फ़्रेमों को छोड़ना ठीक है - जब तक आप एसआई फ़्रेमों से अधिक नहीं छोड़ते हैं, यह झटका नहीं होगा।
jdmayfield

विधि 2 अधिक मजबूत है, क्योंकि यह वास्तव में दो छोरों को मल्टी-टास्किंग कर रहा है, async के माध्यम से आगे और पीछे स्विच नहीं कर रहा है, लेकिन आप अभी भी अपने एसआई फ्रेम को अपने वांछित फ्रेम-रेट से अधिक समय तक बचना चाहते हैं, इसलिए एसआई गतिविधि को विभाजित करना अभी भी हो सकता है वांछनीय है अगर इसमें बहुत अधिक डेटा-हेरफेर हो रहा है जो पूरा करने के लिए एक से अधिक SI फ्रेम लेगा।
jdmayfield

मुझे लगा कि यह ध्यान देने योग्य है, ब्याज के एक नोट के रूप में, यह जोड़ा हुआ लूप इस तरह से वास्तव में Chromes DevTools में पंजीकृत करता है कि GPU सेट-इन्टरवल लूप में निर्दिष्ट फ्रेम-दर पर चल रहा है! यह केवल आरएएफ फ्रेम दिखाई देता है जिसमें ग्राफिकल परिवर्तन होते हैं जिन्हें एफपीएस मीटर द्वारा फ्रेम के रूप में गिना जाता है। तो आरएएफ फ्रेम जिसमें केवल गैर-ग्राफिकल काम, या यहां तक ​​कि केवल खाली लूप होते हैं, जहां तक ​​जीपीयू का संबंध है, गिनती नहीं करते हैं। मुझे यह दिलचस्प लगता है कि आगे के शोध के लिए एक शुरुआती बिंदु है।
jdmayfield

मेरा मानना ​​है कि इस समाधान में यह समस्या है कि यह चलता रहता है जब आरएएफ निलंबित हो जाता है, उदाहरण के लिए क्योंकि उपयोगकर्ता दूसरे टैब पर स्विच करता है।
N4ppeL

1
PS मैंने कुछ पठन किया और ऐसा लगता है कि अधिकांश ब्राउज़र समयबद्ध घटनाओं को पृष्ठभूमि के टैब में एक बार प्रति सेकंड तक सीमित कर देते हैं (जिसे शायद किसी तरह से भी संभालना चाहिए)। यदि आप अभी भी समस्या को संबोधित करना चाहते हैं और दिखाई नहीं देने पर पूरी तरह से विराम देते हैं, तो visibilitychangeघटना प्रतीत होती है ।
N4ppeL

3

एक विशिष्ट एफपीएस के लिए आसानी से थ्रॉटल कैसे करें:

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

स्रोत: आइज़ैक सूकिन द्वारा जावास्क्रिप्ट गेम लूप्स और टाइमिंग की विस्तृत व्याख्या


1
अगर मेरा मॉनिटर 60 एफपीएस पर चलता है और मैं चाहता हूं कि मेरा गेम 58 एफपीएस पर चले तो मैंने अधिकतम एफपीएस = 58 सेट किया, यह इसे 30 एफपीएस पर चलाएगा क्योंकि यह हर 2 वें फ्रेम को छोड़ देगा।
कर्टिस

हां, मैंने यह कोशिश की। मैं वास्तव में आरएएफ को ही नहीं थोपना चुनता हूं - केवल सेटटाइमआउट द्वारा परिवर्तन को अद्यतन किया जाता है। कम से कम क्रोम में, यह प्रभावी एफपीएस सेटटाउट्स की गति से चलने का कारण बनता है, देवटूल में रीडिंग के अनुसार। बेशक यह केवल वीडियो कार्ड की गति से वास्तविक वीडियो फ्रेम को अपडेट कर सकता है और रिफ्रेश रेट की निगरानी कर सकता है, लेकिन यह विधि कम से कम जानकी के साथ काम करती दिखाई देती है, इसलिए सबसे आसानी से "स्पष्ट" एफपीएस नियंत्रण, जो कि मैं जा रहा हूं।
jdmayfield

चूंकि मैं आरएएफ से अलग जेएस ऑब्जेक्ट्स में सभी मोशन का ट्रैक रखता हूं, इसलिए यह एनीमेशन तर्क, टकराव का पता लगाने या आपको जो कुछ भी ज़रूरत है, वह आरएएफ या सेटटाइम के बिना, थोड़ा अतिरिक्त गणित के साथ, अवधारणात्मक सुसंगत दर पर चल रहा है।
jdmayfield

2

लंघन requestAnimationFrame कारण चिकनी नहीं (वांछित) कस्टम एफपीएस पर एनीमेशन।

// 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());
input{
  width:100px;
}
#tvs{
  color:red;
  padding:0px 25px;
}
H3{
  font-weight:400;
}
<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>

मूल कोड @tavnab द्वारा।


2
var time = 0;
var time_framerate = 1000; //in milliseconds

function animate(timestamp) {
  if(timestamp > time + time_framerate) {
    time = timestamp;    

    //your code
  }

  window.requestAnimationFrame(animate);
}

कृपया यह बताने के लिए कुछ वाक्यों को जोड़ें कि आपका कोड क्या कर रहा है, इसलिए आप अपने उत्तर के लिए और अधिक अपडेट प्राप्त कर सकते हैं।
फजी एनालिसिस

1

मैं हमेशा टाइमस्टैम्प के साथ खिलवाड़ किए बिना इसे बहुत सरल तरीके से करता हूं:

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

1
यदि आपका मॉनिटर 120 एफपीएस है तो यह बहुत तेज चलेगा।
कर्टिस

0

यहाँ मुझे एक अच्छी व्याख्या मिली: CreativeJS.com , एक setTimeou को लपेटने के लिए) फ़ंक्शन के अंदर कॉल करने के लिए अनुरोध किया गया। एक "सादे" अनुरोध के साथ मेरी चिंता यह है कि क्या होगा, "क्या होगा अगर मैं केवल इसे तीन बार एक दूसरे को चेतन करना चाहता हूं ?" यहां तक ​​कि requestAnimationFrame (सेटटाइमआउट के विपरीत) के साथ भी यह है कि यह अभी भी "ऊर्जा" (कुछ) की राशि बर्बाद करता है (जिसका अर्थ है कि ब्राउज़र कोड कुछ कर रहा है, और संभवतः सिस्टम को धीमा कर रहा है) 60 या 120 या फिर कई बार एक सेकंड के रूप में। केवल दो या तीन बार एक दूसरे के विपरीत (जैसा आप चाहते हो)।

अधिकांश समय मैं अपने ब्राउज़र को जावास्क्रिप्ट के साथ केवल इसी कारण से बंद करता हूं । लेकिन, मैं Yosemite 10.10.3 का उपयोग कर रहा हूं, और मुझे लगता है कि इसके साथ कुछ प्रकार की टाइमर समस्या है - कम से कम मेरे पुराने सिस्टम (अपेक्षाकृत पुराने अर्थ 2011) पर।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.