परिचय
आपको अपने वेब ऐप्लिकेशन को ऐनिमेशन, ट्रांज़िशन, और अन्य छोटे यूज़र इंटरफ़ेस (यूआई) इफ़ेक्ट के दौरान रिस्पॉन्सिव और स्मूद बनाना है. यह पक्का करना कि ये इफ़ेक्ट बिना किसी रुकावट के काम करें, "नेटिव" अनुभव या खराब, बिना पॉलिश किए गए अनुभव के बीच का अंतर हो सकता है.
यह लेखों की सीरीज़ का पहला लेख है. इसमें ब्राउज़र में रेंडरिंग की परफ़ॉर्मेंस को ऑप्टिमाइज़ करने के बारे में बताया गया है. सबसे पहले, हम यह जानेंगे कि स्मूद ऐनिमेशन बनाना मुश्किल क्यों है और इसे बनाने के लिए क्या करना ज़रूरी है. साथ ही, हम कुछ आसान और सबसे सही तरीके भी जानेंगे. इनमें से कई आइडिया, "जैंक बस्टर्स" में पेश किए गए थे. यह एक बातचीत थी, जो नैट ड्यूका और मैंने इस साल Google I/O टॉक (वीडियो) में की थी.
पेश है वी-सिंक
पीसी पर गेम खेलने वाले लोग इस शब्द से वाकिफ़ हो सकते हैं, लेकिन वेब पर यह आम नहीं है: v-sync क्या है?
अपने फ़ोन के डिसप्ले को देखें: यह एक तय समय पर रीफ़्रेश होता है. आम तौर पर, यह एक सेकंड में करीब 60 बार रीफ़्रेश होता है. हालांकि, ऐसा हमेशा नहीं होता! वी-सिंक (या वर्टिकल सिंक्रनाइज़ेशन) का मतलब है कि स्क्रीन रीफ़्रेश होने के बीच ही नए फ़्रेम जनरेट करना. इसे स्क्रीन बफ़र में डेटा लिखने की प्रोसेस और उस डेटा को पढ़कर डिसप्ले पर दिखाने वाले ऑपरेटिंग सिस्टम के बीच की रेस कंडिशन की तरह समझा जा सकता है. हम चाहते हैं कि बफ़र किए गए फ़्रेम का कॉन्टेंट, रीफ़्रेश होने के बीच में बदले, न कि रीफ़्रेश होने के दौरान. ऐसा न होने पर, मॉनिटर पर एक फ़्रेम का आधा हिस्सा और दूसरे फ़्रेम का आधा हिस्सा दिखेगा. इससे "टीयरिंग" की समस्या होगी.
स्मूद ऐनिमेशन के लिए, स्क्रीन रीफ़्रेश होने पर हर बार एक नया फ़्रेम तैयार होना चाहिए. इसके दो बड़े असर होते हैं: फ़्रेम टाइमिंग (यानी कि फ़्रेम को कब तक तैयार होना चाहिए) और फ़्रेम बजट (यानी कि ब्राउज़र को फ़्रेम बनाने में कितना समय लगता है). आपके पास फ़्रेम को पूरा करने के लिए, स्क्रीन रीफ़्रेश होने के बीच का समय होता है. जैसे, 60 हर्ट्ज़ की स्क्रीन पर करीब 16 मिलीसेकंड. साथ ही, स्क्रीन पर आखिरी फ़्रेम दिखने के तुरंत बाद, आपको अगला फ़्रेम बनाना शुरू करना होगा.
सही समय पर सही काम करना सबसे ज़रूरी है: requestAnimationFrame
कई वेब डेवलपर, ऐनिमेशन बनाने के लिए हर 16 मिलीसेकंड में setInterval या setTimeout का इस्तेमाल करते हैं. यह कई वजहों से एक समस्या है. हम इस बारे में एक मिनट में ज़्यादा बात करेंगे. हालांकि, खास तौर पर ये चिंताएं हैं:
- JavaScript से टाइमर रिज़ॉल्यूशन सिर्फ़ कुछ मिलीसेकंड का होता है
- अलग-अलग डिवाइसों के लिए रीफ़्रेश रेट अलग-अलग होता है
ऊपर बताई गई फ़्रेम टाइमिंग की समस्या को याद करें: आपको एक पूरा ऐनिमेशन फ़्रेम चाहिए, जो किसी भी JavaScript, DOM मैनिप्युलेशन, लेआउट, पेंटिंग वगैरह के साथ पूरा हो, ताकि अगली स्क्रीन रीफ़्रेश होने से पहले तैयार हो सके. टाइमर का रिज़ॉल्यूशन कम होने पर, स्क्रीन के अगले रीफ़्रेश से पहले ऐनिमेशन फ़्रेम को पूरा करना मुश्किल हो सकता है. हालांकि, स्क्रीन के रीफ़्रेश रेट में अंतर होने की वजह से, तय किए गए टाइमर के साथ ऐसा करना नामुमकिन हो जाता है. टाइमर इंटरवल चाहे जो भी हो, आपको फ़्रेम के लिए तय की गई समयावधि से धीरे-धीरे बाहर निकलना होगा. इसके बाद, आपको एक फ़्रेम छोड़ना होगा. ऐसा तब भी होगा, जब टाइमर मिलीसेकंड की सटीक जानकारी के साथ ट्रिगर होगा. हालांकि, ऐसा नहीं होगा (जैसा कि डेवलपर ने पता लगाया है) -- टाइमर का रिज़ॉल्यूशन इस बात पर निर्भर करता है कि मशीन बैटरी पर है या प्लग इन है. साथ ही, बैकग्राउंड टैब के संसाधनों का ज़्यादा इस्तेमाल करने वगैरह से भी इस पर असर पड़ सकता है. भले ही, ऐसा बहुत कम होता है (मान लें कि हर 16 फ़्रेम में, क्योंकि आप एक मिलीसेकंड से पीछे थे), लेकिन आपको यह दिखेगा: आपको हर सेकंड कई फ़्रेम ड्रॉप करने होंगे. आपको ऐसे फ़्रेम जनरेट करने के लिए भी काम करना होगा जो कभी नहीं दिखाए जाते. इससे पावर और सीपीयू समय बर्बाद होता है. इस समय का इस्तेमाल, अपने ऐप्लिकेशन में अन्य काम करने के लिए किया जा सकता है.
अलग-अलग डिसप्ले के लिए रीफ़्रेश रेट अलग-अलग होते हैं: 60 हर्ट्ज़ आम तौर पर इस्तेमाल होने वाला रीफ़्रेश रेट है. हालांकि, कुछ फ़ोन का रीफ़्रेश रेट 59 हर्ट्ज़ होता है. कुछ लैपटॉप, बैटरी बचाने वाले मोड में 50 हर्ट्ज़ पर आ जाते हैं. कुछ डेस्कटॉप मॉनिटर का रीफ़्रेश रेट 70 हर्ट्ज़ होता है.
रेंडरिंग की परफ़ॉर्मेंस पर चर्चा करते समय, हम फ़्रेम प्रति सेकंड (एफ़पीएस) पर ध्यान देते हैं. हालांकि, फ़्रेम रेट में अंतर होना इससे भी बड़ी समस्या हो सकती है. हमारी आंखें, ऐनिमेशन में होने वाली छोटी और अनियमित रुकावटों को नोटिस करती हैं. ये रुकावटें, खराब टाइमिंग वाले ऐनिमेशन की वजह से हो सकती हैं.
सही समय पर ऐनिमेशन फ़्रेम पाने का तरीका requestAnimationFrame है. इस एपीआई का इस्तेमाल करने का मतलब है कि ब्राउज़र से ऐनिमेशन फ़्रेम का अनुरोध किया जा रहा है. जब ब्राउज़र नया फ़्रेम बनाने वाला होता है, तब आपका कॉलबैक कॉल किया जाता है. ऐसा तब होता है, जब रीफ़्रेश रेट कुछ भी हो.
requestAnimationFrame में अन्य अच्छी प्रॉपर्टी भी हैं:
- बैकग्राउंड टैब में मौजूद ऐनिमेशन रुक जाते हैं. इससे सिस्टम के रिसॉर्स और बैटरी लाइफ़ की बचत होती है.
- अगर सिस्टम, स्क्रीन के रीफ़्रेश रेट पर रेंडरिंग को हैंडल नहीं कर पाता है, तो वह ऐनिमेशन को थ्रॉटल कर सकता है. साथ ही, कॉलबैक को कम बार जनरेट कर सकता है. उदाहरण के लिए, 60 हर्ट्ज़ वाली स्क्रीन पर एक सेकंड में 30 बार. इससे फ़्रेमरेट आधा हो जाता है, लेकिन ऐनिमेशन एक जैसा रहता है. जैसा कि ऊपर बताया गया है, हमारी आंखें फ़्रेमरेट के मुकाबले, फ़्रेमरेट में होने वाले बदलावों को ज़्यादा आसानी से पहचान लेती हैं. एक सेकंड में कुछ फ़्रेम मिस करने वाले 60 हर्ट्ज़ के मुकाबले, 30 हर्ट्ज़ का स्थिर फ़्रेम रेट बेहतर दिखता है.
requestAnimationFrame के बारे में पहले ही काफ़ी चर्चा हो चुकी है. इसलिए, इसके बारे में ज़्यादा जानकारी के लिए, क्रिएटिव JS के इस लेख जैसे लेख पढ़ें. हालांकि, ऐनिमेशन को बेहतर बनाने के लिए यह पहला ज़रूरी कदम है.
फ़्रेम बजट
हम चाहते हैं कि स्क्रीन के हर बार रीफ़्रेश होने पर एक नया फ़्रेम तैयार हो. इसलिए, नया फ़्रेम बनाने के लिए, रीफ़्रेश होने के बीच का समय ही मिलता है. 60 हर्ट्ज़ वाले डिसप्ले पर, इसका मतलब है कि हमारे पास सभी JavaScript को चलाने, लेआउट बनाने, पेंट करने, और ब्राउज़र को फ़्रेम दिखाने के लिए जो भी करना है उसे करने के लिए करीब 16 मिलीसेकंड हैं. इसका मतलब है कि अगर आपके requestAnimationFrame कॉलबैक में मौजूद JavaScript को चलने में 16 मि॰से॰ से ज़्यादा समय लगता है, तो वी-सिंक के लिए समय पर फ़्रेम नहीं बनाया जा सकता!
16 मिलीसेकंड बहुत ज़्यादा समय नहीं होता. खुशकिस्मती से, Chrome के डेवलपर टूल की मदद से यह ट्रैक किया जा सकता है कि requestAnimationFrame कॉलबैक के दौरान, आपका फ़्रेम बजट खत्म हो रहा है या नहीं.
डेवलपर टूल की टाइमलाइन खोलने और इस ऐनिमेशन की रिकॉर्डिंग करने से, तुरंत पता चलता है कि ऐनिमेशन बनाते समय हम बजट से काफ़ी ज़्यादा खर्च कर रहे हैं. टाइमलाइन में "फ़्रेम" पर स्विच करें और देखें:
उन requestAnimationFrame (rAF) कॉलबैक में 200 मि॰से॰ से ज़्यादा समय लग रहा है. हर 16 मि॰से॰ में एक फ़्रेम दिखाने के लिए, यह समय बहुत ज़्यादा है! इनमें से किसी एक लंबे rAF कॉलबैक को खोलने पर, यह पता चलता है कि अंदर क्या हो रहा है: इस मामले में, बहुत सारे लेआउट.
पॉल के वीडियो में, रीलेआउट की खास वजह (यह scrollTop को पढ़ रहा है) और इससे बचने के तरीके के बारे में ज़्यादा जानकारी दी गई है. हालांकि, यहां ध्यान देने वाली बात यह है कि कॉलबैक में जाकर यह पता लगाया जा सकता है कि इतना समय क्यों लग रहा है.
ध्यान दें कि 16 मिलीसेकंड के फ़्रेम टाइम में क्या होता है. फ़्रेम में मौजूद खाली जगह, हेड रूम होती है. इसमें आपको ज़्यादा काम करना होता है या ब्राउज़र को बैकग्राउंड में काम करने देना होता है. यह खाली जगह एक अच्छी चीज़ है.
जंक का अन्य सोर्स
JavaScript की मदद से चलने वाले ऐनिमेशन को चलाने की कोशिश करते समय, सबसे बड़ी समस्या यह होती है कि अन्य चीज़ें आपके rAF कॉलबैक में रुकावट डाल सकती हैं. यहां तक कि उसे चलने से भी रोक सकती हैं. भले ही, आपका rAF कॉलबैक छोटा हो और कुछ ही मिलीसेकंड में चलता हो, लेकिन अन्य गतिविधियां अचानक आ सकती हैं और बिना किसी रुकावट के किसी भी समय तक चल सकती हैं. जैसे, हाल ही में आए XHR को प्रोसेस करना, इनपुट इवेंट हैंडलर चलाना या टाइमर पर शेड्यूल किए गए अपडेट चलाना. मोबाइल डिवाइसों पर कभी-कभी इन इवेंट को प्रोसेस करने में सैकड़ों मिलीसेकंड लग सकते हैं. इस दौरान, आपका ऐनिमेशन पूरी तरह से रुक जाएगा. हम ऐनिमेशन में आने वाली इन रुकावटों को जैंक कहते हैं.
इन स्थितियों से बचने का कोई जादुई तरीका नहीं है. हालांकि, आर्किटेक्चर से जुड़े कुछ सबसे सही तरीके अपनाकर, खुद को सफलता के लिए तैयार किया जा सकता है:
- इनपुट हैंडलर में ज़्यादा प्रोसेसिंग न करें! बहुत ज़्यादा जेएस का इस्तेमाल करने या onscroll हैंडलर के दौरान पूरे पेज को फिर से व्यवस्थित करने की कोशिश करने से, पेज पर कॉन्टेंट लोड होने में बहुत ज़्यादा समय लगता है.
- जितना हो सके, प्रोसेसिंग (यानी कि ऐसी कोई भी चीज़ जिसे चलाने में ज़्यादा समय लगेगा) को अपने rAF कॉलबैक या वेब वर्कर में पुश करें.
- अगर rAF कॉलबैक में काम को पुश किया जाता है, तो उसे छोटे-छोटे हिस्सों में बांटने की कोशिश करें, ताकि हर फ़्रेम में थोड़ा-थोड़ा काम प्रोसेस हो या किसी अहम ऐनिमेशन के खत्म होने तक उसे रोकें. इस तरह, छोटे rAF कॉलबैक को चलाना जारी रखा जा सकता है और ऐनिमेशन को आसानी से चलाया जा सकता है.
इनपुट हैंडलर के बजाय, requestAnimationFrame कॉलबैक में प्रोसेसिंग को पुश करने का तरीका जानने के लिए, पॉल लुईस का लेख Leaner, Meaner, Faster Animations with requestAnimationFrame पढ़ें.
सीएसएस ऐनिमेशन
अपने इवेंट और rAF कॉलबैक में लाइटवेट JS का इस्तेमाल करने से बेहतर क्या हो सकता है? कोई JS नहीं.
हमने पहले बताया था कि rAF कॉलबैक में रुकावट आने से रोकने का कोई आसान तरीका नहीं है. हालांकि, सीएसएस ऐनिमेशन का इस्तेमाल करके, कॉलबैक की ज़रूरत को पूरी तरह से खत्म किया जा सकता है. खास तौर पर, Android के लिए Chrome पर (और अन्य ब्राउज़र भी ऐसी ही सुविधाओं पर काम कर रहे हैं), सीएसएस ऐनिमेशन में एक बहुत ही अच्छी प्रॉपर्टी होती है. इसकी मदद से, ब्राउज़र अक्सर उन्हें चला सकता है, भले ही JavaScript चल रहा हो.
जैंक के बारे में ऊपर दिए गए सेक्शन में एक बात साफ़ तौर पर बताई गई है: ब्राउज़र एक समय में सिर्फ़ एक काम कर सकते हैं. यह पूरी तरह से सच नहीं है, लेकिन यह एक अच्छा अनुमान है: किसी भी समय ब्राउज़र, JS को चला सकता है, लेआउट बना सकता है या पेंटिंग कर सकता है. हालांकि, एक समय में सिर्फ़ एक ही काम किया जा सकता है. इसकी पुष्टि, Dev Tools के टाइमलाइन व्यू में की जा सकती है. इस नियम का एक अपवाद, 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);
}
}
सीएसएस ऐनिमेशन इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, एमडीएन पर मौजूद यह लेख पढ़ें.
सेशन की समाप्ति
संक्षेप में कहें, तो:
- ऐनिमेशन बनाते समय, हर स्क्रीन रीफ़्रेश के लिए फ़्रेम बनाना ज़रूरी है. Vsync'd ऐनिमेशन से, ऐप्लिकेशन के इस्तेमाल का अनुभव काफ़ी बेहतर हो जाता है.
- Chrome और अन्य मॉडर्न ब्राउज़र में, वीसिंक किए गए ऐनिमेशन पाने का सबसे अच्छा तरीका है कि सीएसएस ऐनिमेशन का इस्तेमाल किया जाए. जब आपको सीएसएस ऐनिमेशन से ज़्यादा सुविधाएं चाहिए हों, तो requestAnimationFrame-based animation सबसे सही तकनीक है.
- rAF ऐनिमेशन को सही तरीके से चलाने के लिए, पक्का करें कि अन्य इवेंट हैंडलर, आपके rAF कॉलबैक के चलने में रुकावट न डालें. साथ ही, rAF कॉलबैक को छोटा रखें (<15 मि॰से॰).
आखिर में, vsync'd ऐनिमेशन सिर्फ़ आसान यूआई ऐनिमेशन पर लागू नहीं होता. यह Canvas2D ऐनिमेशन, WebGL ऐनिमेशन, और स्टैटिक पेजों पर स्क्रोल करने पर भी लागू होता है. इस सीरीज़ के अगले लेख में, हम इन कॉन्सेप्ट को ध्यान में रखते हुए, स्क्रोलिंग की परफ़ॉर्मेंस के बारे में गहराई से जानेंगे.
ऐनिमेशन जनरेट करने का आनंद लें!