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

Tom Wiltzius
Tom Wiltzius

परिचय

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

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

पेश है V-sync

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

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

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

समय सब कुछ है: requestAnimationFrame

कई वेब डेवलपर, ऐनिमेशन बनाने के लिए हर 16 मिलीसेकंड में setInterval या setTimeout का इस्तेमाल करते हैं. इसकी कई वजहें हो सकती हैं. हम इस बारे में थोड़ी देर में ज़्यादा जानकारी देंगे. हालांकि, इन वजहों से खास तौर पर चिंता होती है:

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

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

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

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

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

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

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

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

फ़्रेम का बजट

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

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

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

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

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

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

अपडेट किया गया डेमो, जिसमें लेआउट काफ़ी कम किया गया है
बहुत कम लेआउट वाला अपडेट किया गया डेमो

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

रुकावट का दूसरा सोर्स

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

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

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

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

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

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

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

ऊपर दिए गए सेक्शन में, जंक के बारे में एक बात साफ़ तौर पर कही गई है: ब्राउज़र एक बार में सिर्फ़ एक काम कर सकते हैं. यह बात पूरी तरह से सही नहीं है, लेकिन यह एक अच्छी धारणा है: किसी भी समय ब्राउज़र, JS चला सकता है, लेआउट बना सकता है या पेज को पेंट कर सकता है. हालांकि, एक बार में सिर्फ़ एक काम किया जा सकता है. इसकी पुष्टि, Dev Tools के टाइमलाइन व्यू में की जा सकती है. इस नियम का एक अपवाद, Chrome for Android पर सीएसएस ऐनिमेशन है. हालांकि, फ़िलहाल डेस्कटॉप पर 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 मिलीसेकंड तक चलता है. इससे, ऐप्लिकेशन में रुकावट आती है. हालांकि, अगर उस ऐनिमेशन को सीएसएस ऐनिमेशन की मदद से चलाया जाता है, तो झटका नहीं लगता.

(ध्यान रखें कि इस लेख को लिखने के समय, सीएसएस ऐनिमेशन सिर्फ़ Chrome for Android पर बिना रुकावट के चलता है, न कि डेस्कटॉप पर 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);
    }
  }

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

आखिर में खास जानकारी

खास जानकारी:

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

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

ऐनिमेशन बनाने का आनंद लें!

रेफ़रंस