बेहतर रेंडरिंग के लिए जैंक बस्टिंग

Tom Wiltzius
Tom Wiltzius

शुरुआती जानकारी

ऐनिमेशन, ट्रांज़िशन, और अन्य छोटे यूज़र इंटरफ़ेस (यूआई) इफ़ेक्ट इस्तेमाल करते समय, आपको अपने वेब ऐप्लिकेशन से रिस्पॉन्सिव और आसान महसूस कराना है. यह पक्का करने का मतलब है कि ये इफ़ेक्ट जैंक-फ़्री हैं. इसका मतलब है कि अलग-अलग "नेटिव" फ़ील या खराब, लेकिन बिना झलक वाले इफ़ेक्ट.

यह लेखों की इस सीरीज़ का पहला लेख है. इसमें ब्राउज़र में रेंडरिंग की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के बारे में जानकारी दी गई है. शुरुआत करने के लिए, हम आपको बताएंगे कि आसान ऐनिमेशन बनाना क्यों मुश्किल है और इसे हासिल करने के लिए क्या करना चाहिए. साथ ही, हम आपको कुछ आसान सबसे सही तरीकों के बारे में भी बताएंगे. इनमें से कई आइडिया पहली बार "JAंक बस्टर्स" में पेश किए गए थे. यह बातचीत Nat Duca है. मैंने इस साल Google I/O टॉक प्रोग्राम (वीडियो) में ये आइडिया दिए थे.

पेश है V-sync

पीसी पर खेले जाने वाले गेम के गेमर इस शब्द के बारे में जानते होंगे, लेकिन वेब पर इस बारे में आम बात है: v-sync क्या है?

अपने फ़ोन के डिस्प्ले पर ध्यान दें: यह नियमित अंतराल पर रीफ़्रेश होता है, आमतौर पर (लेकिन हमेशा नहीं!). यह एक सेकंड में करीब 60 बार रीफ़्रेश होता है. वी-सिंक (या वर्टिकल सिंक्रोनाइज़ेशन) का मतलब है, स्क्रीन रीफ़्रेश होने के बीच में ही नए फ़्रेम जनरेट करना. आपको लग सकता है कि यह एक ऐसी रेस कंडिशन है जो स्क्रीन बफ़र में डेटा लिखने वाली प्रोसेस और उस डेटा को डिसप्ले पर रखने के लिए ऑपरेटिंग सिस्टम को पढ़ने के बीच में होती है. हम चाहते हैं कि बफ़र किए गए फ़्रेम का कॉन्टेंट, इन रीफ़्रेश के बीच में बदलता रहे, न कि इन रीफ़्रेश के दौरान. ऐसा न करने पर, मॉनिटर एक फ़्रेम का आधा और दूसरे फ़्रेम का आधा हिस्सा दिखाएगा, जिससे "आंसू" आ जाएगा.

स्मूद ऐनिमेशन के लिए, आपको हर बार स्क्रीन रीफ़्रेश होने पर एक नए फ़्रेम की ज़रूरत होगी. इसके दो बड़े प्रभाव होते हैं: फ़्रेम टाइमिंग (यानी, जब फ़्रेम को तैयार करने की ज़रूरत हो) और फ़्रेम बजट (इसका मतलब है कि ब्राउज़र को कितनी देर तक फ़्रेम तैयार करना है). आपके पास स्क्रीन को रीफ़्रेश करने के बीच का समय सिर्फ़ एक फ़्रेम को पूरा करने के लिए होता है (~60 हर्ट्ज़ स्क्रीन पर 16 मि॰से॰) और स्क्रीन पर पिछला फ़्रेम आते ही आपको अगला फ़्रेम बनाना है.

हर समय चाहिए: requestAnimationFrame

कई वेब डेवलपर, ऐनिमेशन बनाने के लिए हर 16 मिलीसेकंड में setInterval या setTimeout का इस्तेमाल करते हैं. यह कई कारणों से समस्या है (और हम एक मिनट में इस पर और चर्चा करेंगे), लेकिन कुछ खास चिंता ये हैं:

  • JavaScript के टाइमर का रिज़ॉल्यूशन सिर्फ़ कई मिलीसेकंड के हिसाब से होता है
  • हर डिवाइस की रीफ़्रेश दर अलग-अलग होती है

फ़्रेम टाइमिंग की ऊपर बताई गई समस्या को फिर से याद करें: अगली स्क्रीन रीफ़्रेश होने से पहले तैयार रहने के लिए आपको एक पूरा ऐनिमेशन फ़्रेम चाहिए, जो JavaScript, DOM मैनिप्यूलेशन, लेआउट, पेंटिंग वगैरह के साथ पूरा हो. टाइमर का रिज़ॉल्यूशन कम होने पर, हो सकता है कि अगली स्क्रीन रीफ़्रेश होने से पहले, ऐनिमेशन फ़्रेम पूरा करना मुश्किल हो. हालांकि, स्क्रीन रीफ़्रेश होने की दर में बदलाव होने की वजह से, किसी तय टाइमर के साथ ऐसा नहीं हो पाता. इससे कोई फ़र्क़ नहीं पड़ता कि टाइमर का इंटरवल क्या है. ऐसा करने पर, धीरे-धीरे फ़्रेम के लिए टाइमिंग विंडो से बाहर जाकर एक फ़्रेम छूट जाएगा. ऐसा तब भी होगा, जब टाइमर मिलीसेकंड तक सटीक तरीके से फ़ायर हो, लेकिन ऐसा नहीं होगा (जैसा कि डेवलपर ने खोजा) किया है -- टाइमर का रिज़ॉल्यूशन इस बात पर निर्भर करता है कि मशीन बैटरी पर है या प्लग-इन है, और ऐसा बहुत कम मामलों में (जैसे, हर 16 फ़्रेम होने की वजह से हुआ है, क्योंकि आपने मिलीसेकंड में एक सेकंड पीछे छोड़ दिया) इस बात पर निर्भर करता है कि डिवाइस, बैटरी पर है या प्लग-इन है. आप ऐसे फ़्रेम भी जनरेट करने का काम कर रहे होंगे जो कभी दिखाई नहीं देंगे. इससे पावर और सीपीयू का समय बर्बाद हो जाता है, जिससे आपको ऐप्लिकेशन में दूसरे काम करने में समय लग सकता है.

अलग-अलग डिसप्ले में, रीफ़्रेश दर अलग-अलग होती है: आम तौर पर, 60 हर्ट्ज़ का रीफ़्रेश दर 60 हर्ट्ज़ पर होता है. हालांकि, कुछ फ़ोन में 59 हर्ट्ज़, कम पावर वाले मोड में कुछ लैपटॉप 50 हर्ट्ज़, और कुछ डेस्कटॉप मॉनिटर 70 हर्ट्ज़ वाले होते हैं.

रेंडरिंग की परफ़ॉर्मेंस के बारे में बात करते समय, हम फ़्रेम प्रति सेकंड (एफ़पीएस) पर फ़ोकस करते हैं. हालांकि, इसमें फ़र्क़ आना एक बड़ी समस्या हो सकती है. हमारा मानना है कि ऐनिमेशन में छोटे-छोटे, अनियमित हिट और समय की कमी है. इसकी वजह यह है कि अगर कोई ऐनिमेशन, सही समय पर सेट न हो, तो उसे देखकर ऐसा हो सकता है.

सही समय पर तय किए गए ऐनिमेशन फ़्रेम पाने का तरीका requestAnimationFrame है. इस एपीआई का इस्तेमाल करने का मतलब है कि आपने ब्राउज़र से ऐनिमेशन फ़्रेम मांगा है. जब ब्राउज़र जल्द ही नया फ़्रेम बनाने वाला होगा, तब आपके कॉलबैक को कॉल किया जाएगा. रीफ़्रेश दर चाहे जो भी हो, ऐसा होता है.

requestAnimationFrame में अन्य अच्छी प्रॉपर्टी भी हैं:

  • बैकग्राउंड में चलने वाले टैब में ऐनिमेशन रोक दिए जाते हैं. इससे सिस्टम के संसाधनों और बैटरी लाइफ़ का खतरा बना रहता है.
  • अगर सिस्टम, स्क्रीन की रीफ़्रेश दर पर रेंडरिंग को हैंडल नहीं कर पाता, तो यह ऐनिमेशन को थ्रॉटल कर सकता है और बार-बार कॉलबैक नहीं जनरेट कर सकता. जैसे, 60 हर्ट्ज़ स्क्रीन पर एक सेकंड में 30 बार. हालांकि यह फ़्रेमरेट के आधे हिस्से में घट जाता है, लेकिन यह ऐनिमेशन को एक जैसा बनाए रखता है. साथ ही, जैसा कि ऊपर बताया गया है, हमारी आंखें फ़्रेमरेट की तुलना में अंतर पर ज़्यादा ध्यान देती हैं. स्थिर 30 हर्ट्ज़ 60 हर्ट्ज़ से बेहतर दिखता है और कुछ फ़्रेम प्रति सेकंड छूट जाता है.

requestAnimationFrame के बारे में पहले से ही हर जगह चर्चा की जा चुकी है. इसलिए, इस बारे में ज़्यादा जानकारी के लिए क्रिएटिव JS से यह लेख पढ़ें, लेकिन यह ऐनिमेशन को बेहतर बनाने का पहला अहम कदम है.

बजट फ़्रेम करें

हमें लगता है कि हर बार स्क्रीन रीफ़्रेश होने पर, एक नया फ़्रेम तैयार हो जाएगा. इसलिए, फ़्रेम को रीफ़्रेश करने के बीच ही समय होता है कि नया फ़्रेम बनाने का काम पूरा हो जाए. 60 हर्ट्ज़ के डिसप्ले का मतलब है कि सभी JavaScript को चलाने, लेआउट तैयार करने, पेंट करने और ब्राउज़र को फ़्रेम आउट करने के लिए और कुछ करने के लिए, हमारे पास करीब 16 मि॰से॰ ही हैं. इसका मतलब है कि अगर आपके requestAnimationFrame कॉलबैक के अंदर JavaScript को चलने में 16 मि॰से॰ से ज़्यादा समय लगता है, तो आपको v-सिंक के लिए समय पर फ़्रेम बनाने की कोई उम्मीद नहीं है!

16 मिलीसेकंड ज़्यादा समय नहीं है. अच्छी बात यह है कि Chrome के डेवलपर टूल की मदद से यह ट्रैक किया जा सकता है कि अगर आपको requestAnimationFrame कॉलबैक के दौरान अपना फ़्रेम बजट इस्तेमाल करना है.

Dev टूल की टाइमलाइन खोलने और इस ऐनिमेशन की रिकॉर्डिंग का इस्तेमाल करने से तुरंत पता चलता है कि ऐनिमेशन बनाते समय हमने बजट से ज़्यादा खर्च किया है. टाइमलाइन में, "फ़्रेम" पर स्विच करें और एक नज़र डालें:

बहुत ज़्यादा लेआउट वाला डेमो
बहुत ज़्यादा लेआउट वाला डेमो

ये requestAnimationFrame (rAF) कॉलबैक में 200 मि॰से॰ से ज़्यादा समय लगता है. यह इतना ज़्यादा है कि हर 16 मि॰से॰ के फ़्रेम को सही नहीं माना जा सकता! लंबे rAF कॉलबैक में से किसी एक को खोलने से पता चलता है कि अंदर क्या चल रहा है: इस मामले में, बहुत सारे लेआउट हैं.

पॉल के वीडियो में, ब्रॉडकास्ट की खास वजह और इससे बचने के बारे में ज़्यादा जानकारी दी गई है. जैसे, scrollTop. हालांकि, यहां पॉइंट यह है कि कॉलबैक में जाकर, यह जांच की जा सकती है कि इसमें कितनी देर लग रही है.

बहुत कम लेआउट के साथ अपडेट किया गया डेमो
बहुत कम लेआउट के साथ अपडेट किया गया डेमो

16 मि॰से॰ फ़्रेम टाइम पर ध्यान दें. फ़्रेम में वह खाली जगह वह हेडरूम है, जिसमें आपको ज़्यादा काम करना पड़ता है (या ब्राउज़र को वह काम करने दें जो उसे बैकग्राउंड में करने की ज़रूरत है). यह खाली जगह अच्छी बात है.

जैंक का दूसरा स्रोत

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

इन स्थितियों से बचने का कोई तरीका नहीं है. हालांकि, घर डिज़ाइन करने के कुछ सबसे सही तरीके अपनाकर, सफलता हासिल की जा सकती है:

  • इनपुट हैंडलर में ज़्यादा प्रोसेसिंग न करें! बहुत सारे JS करना या पूरे पेज को फिर से व्यवस्थित करने की कोशिश करना. उदाहरण के लिए, एक ऑनस्क्रोल हैंडलर की वजह से बहुत सारे लोगों को नुकसान हो रहा है.
  • ज़्यादा से ज़्यादा प्रोसेसिंग (पढ़ें: ऐसी कोई भी चीज़ जिसे चलने में ज़्यादा समय लगेगा) को अपने आरएएफ़ कॉलबैक या वेब वर्कर में डालें.
  • अगर आप काम को rAF कॉलबैक में पुश करते हैं, तो उसे अलग-अलग हिस्सों में बांटने की कोशिश करें, ताकि हर फ़्रेम का सिर्फ़ थोड़ा हिस्सा प्रोसेस किया जा सके या किसी ज़रूरी ऐनिमेशन के खत्म होने तक की देरी करें -- इस तरह, छोटे rAF कॉलबैक चलाना जारी रखा जा सकता है और आसानी से ऐनिमेट किया जा सकता है.

इनपुट हैंडलर के बजाय, requestAnimationFrame कॉलबैक में प्रोसेसिंग को पुश करने का तरीका बताने वाले बेहतरीन ट्यूटोरियल के लिए, पॉल लुइस का लेख Leaner, Meaner, Fast Animations with requestAnimationFrame पढ़ें.

सीएसएस ऐनिमेशन

आपके इवेंट और rAF कॉलबैक में, लाइटवेट JS से बेहतर क्या हो सकता है? कोई JS नहीं.

पहले हमने कहा था कि आपके rAF कॉलबैक में रुकावट डालने से बचने के लिए कोई आसान तरीका नहीं है. हालांकि, इनकी ज़रूरत पूरी तरह से रोकने के लिए, सीएसएस ऐनिमेशन का इस्तेमाल किया जा सकता है. Android के लिए Chrome पर खास तौर पर (और दूसरे ब्राउज़र मिलती-जुलती सुविधाओं पर काम कर रहे हैं), सीएसएस ऐनिमेशन में वह ज़रूरी प्रॉपर्टी होती है जिसे ब्राउज़र अक्सर चलाता है, भले ही JavaScript चल रहा हो.

जैंक के बारे में ऊपर दिए गए सेक्शन में साफ़ तौर पर जानकारी दी गई है: ब्राउज़र एक बार में सिर्फ़ एक ही काम कर सकते हैं. यह पूरी तरह सच नहीं है, लेकिन यह एक अच्छा अनुमान है कि ब्राउज़र किसी भी समय JS चला सकता है, लेआउट या पेंटिंग कर सकता है, लेकिन एक बार में सिर्फ़ एक ही चला सकता है. इसकी पुष्टि, Dev टूल के टाइमलाइन व्यू में की जा सकती है. इस नियम का एक अपवाद है, Android के लिए Chrome पर सीएसएस ऐनिमेशन है (और जल्द ही डेस्कटॉप Chrome पर, हालांकि अभी तक नहीं).

जब भी संभव हो, तो सीएसएस ऐनिमेशन का इस्तेमाल करने से आपका ऐप्लिकेशन आसान हो जाता है. साथ ही, JavaScript के चलने के दौरान भी ऐनिमेशन बेहतर तरीके से चलते हैं.

  // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ for info on rAF polyfills
  rAF = window.requestAnimationFrame;

  var degrees = 0;
  function update(timestamp) {
    document.querySelector('#foo').style.webkitTransform = "rotate(" + degrees + "deg)";
    console.log('updated to degrees ' + degrees);
    degrees = degrees + 1;
    rAF(update);
  }
  rAF(update);

अगर आप बटन पर क्लिक करते हैं, तो JavaScript 180 मिलीसेकंड तक चलता है, जिससे जैंक हो जाता है. लेकिन अगर इसके बजाय हम उस ऐनिमेशन को सीएसएस ऐनिमेशन के साथ चलाते हैं, तो अब जैंक नहीं होता.

(याद रखें कि यह लिखते समय, सीएसएस ऐनिमेशन, Android के लिए Chrome पर सिर्फ़ जैंक-फ़्री है, डेस्कटॉप Chrome पर नहीं.)

  /* tools like Modernizr (http://modernizr.com/) can help with CSS polyfills */
  #foo {
    +animation-duration: 3s;
    +animation-timing-function: linear;
    +animation-animation-iteration-count: infinite;
    +animation-animation-name: rotate;
  }

  @+keyframes: rotate; {
    from {
      +transform: rotate(0deg);
    }
    to {
      +transform: rotate(360deg);
    }
  }

सीएसएस ऐनिमेशन का इस्तेमाल करने के बारे में ज़्यादा जानकारी के लिए, एमडीएन पर इस तरह का लेख पढ़ें.

रैपअप

इसका मतलब है:

  1. ऐनिमेट करते समय, हर स्क्रीन रीफ़्रेश के लिए फ़्रेम बनाना बहुत ज़रूरी होता है. Vsync का ऐनिमेशन, किसी ऐप्लिकेशन को कैसा महसूस कराता है, इस पर बहुत ज़्यादा सकारात्मक असर डालता है.
  2. Chrome और दूसरे मॉडर्न ब्राउज़र में vsync'd ऐनिमेशन पाने का सबसे अच्छा तरीका सीएसएस ऐनिमेशन का इस्तेमाल करना है. अगर आपको सीएसएस ऐनिमेशन की तुलना में ज़्यादा लचीलेपन की ज़रूरत है, तो requestAnimationFrame-आधारित ऐनिमेशन का सबसे अच्छा तरीका होता है.
  3. आरएएफ़ ऐनिमेशन को बेहतर और खुश रखने के लिए, पक्का करें कि दूसरे इवेंट हैंडलर की वजह से आपके आरएएफ़ कॉलबैक को चलाने में कोई रुकावट न आ रही हो. साथ ही, आरएएफ़ कॉलबैक को छोटा (15 मि॰से॰ से कम) रखें.

आखिर में, vsync'd एनिमेशन सिर्फ़ सामान्य यूज़र इंटरफ़ेस (यूआई) ऐनिमेशन पर लागू नहीं होता -- यह Canvas2D ऐनिमेशन, WebGL ऐनिमेशन, और यहां तक कि स्टैटिक पेजों पर स्क्रोल करने पर भी लागू होता है. इस सीरीज़ के अगले लेख में, हम इन सिद्धांतों को ध्यान में रखते हुए स्क्रोलिंग परफ़ॉर्मेंस के बारे में जानेंगे.

ऐनिमेशन तैयार करते रहें!

रेफ़रंस