परफ़ॉर्मेंस को बेहतर और छोटा करने वाले ऐनिमेशन बनाना

Stephen McGruer
Stephen McGruer

कम शब्दों में कहा जाए तो

क्लिप को ऐनिमेट करते समय, स्केल ट्रांसफ़ॉर्म का इस्तेमाल करें. ऐनिमेशन के दौरान, बच्चों के चेहरे को खिंचने और टेढ़ा होने से रोकने के लिए, उन्हें काउंटर-स्केल किया जा सकता है.

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

उदाहरण के लिए, बड़ा होने वाला मेन्यू:

इसे बनाने के कुछ विकल्प, दूसरे विकल्पों के मुकाबले बेहतर परफ़ॉर्म करते हैं.

गलत: कंटेनर एलिमेंट की चौड़ाई और ऊंचाई को ऐनिमेट करना

कंटेनर एलिमेंट की चौड़ाई और ऊंचाई को ऐनिमेट करने के लिए, थोड़ी सी सीएसएस का इस्तेमाल किया जा सकता है.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

इस तरीके की तुरंत समस्या यह है कि इसमें width और height को ऐनिमेट करना ज़रूरी है. इन प्रॉपर्टी के लिए लेआउट का हिसाब लगाना ज़रूरी होता है. साथ ही, ये ऐनिमेशन के हर फ़्रेम पर नतीजे दिखाते हैं. ऐसा करना काफ़ी महंगा हो सकता है. आम तौर पर, इससे आपको 60fps की सुविधा नहीं मिलती. अगर आपको इस बारे में जानकारी नहीं है, तो रेंडरिंग की परफ़ॉर्मेंस से जुड़ी हमारी गाइड पढ़ें. इनमें, रेंडरिंग की प्रोसेस के काम करने के तरीके के बारे में ज़्यादा जानकारी मिल सकती है.

गलत: सीएसएस क्लिप या clip-path प्रॉपर्टी का इस्तेमाल करना

width और height को ऐनिमेट करने के विकल्प के तौर पर, clip प्रॉपर्टी का इस्तेमाल किया जा सकता है. हालांकि, इस प्रॉपर्टी का इस्तेमाल अब नहीं किया जा सकता. इसके अलावा, अगर आप चाहें, तो इसके बजाय clip-path इस्तेमाल किया जा सकता है. हालांकि, clip के मुकाबले clip-path का इस्तेमाल कम किया जाता है. हालांकि, clip का इस्तेमाल नहीं किया जा सकता. सही है। हालांकि, निराश न हों. यह वह समाधान नहीं है जो आपको चाहिए था!

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

मेन्यू एलिमेंट के width और height को ऐनिमेट करने के मुकाबले, यह तरीका बेहतर है. हालांकि, इसकी एक कमी यह है कि यह अब भी पेंट को ट्रिगर करता है. अगर आपको clip प्रॉपर्टी का इस्तेमाल करना है, तो यह ज़रूरी है कि जिस एलिमेंट पर यह काम कर रहा है वह एलिमेंट पूरी तरह से या तय जगह पर हो. इसके लिए, आपको थोड़ी मेहनत करनी पड़ सकती है.

अच्छा: स्केल को ऐनिमेट करना

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

रेंडरिंग की परफ़ॉर्मेंस से जुड़ी ज़्यादातर चीज़ों की तरह, इस तरीके का भी एक नुकसान है. इसे सेट अप करने में थोड़ा समय लगता है. हालांकि, यह पूरी तरह से फ़ायदेमंद है!

पहला चरण: शुरू और खत्म होने की स्थितियों का हिसाब लगाना

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

function calculateCollapsedScale () {
    // The menu title can act as the marker for the collapsed state.
    const collapsed = menuTitle.getBoundingClientRect();

    // Whereas the menu as a whole (title plus items) can act as
    // a proxy for the expanded state.
    const expanded = menu.getBoundingClientRect();
    return {
    x: collapsed.width / expanded.width,
    y: collapsed.height / expanded.height
    };
}

मेन्यू जैसे किसी आइटम के लिए, हम यह अनुमान लगा सकते हैं कि वह अपने सामान्य स्केल (1, 1) में शुरू होगा. यह नैचुरल स्केल, ऑब्जेक्ट के बड़े किए गए वर्शन को दिखाता है. इसका मतलब है कि आपको स्केल किए गए वर्शन (जिसका हिसाब ऊपर लगाया गया था) से, उस नैचुरल स्केल पर वापस एनिमेट करना होगा.

लेकिन रुकिए! क्या इससे मेन्यू के कॉन्टेंट का स्केल भी बढ़ जाएगा? हां, जैसा कि यहां दिख रहा है.

तो इस बारे में क्या किया जा सकता है? कॉन्टेंट पर काउंटर-ट्रांसफ़ॉर्म लागू किया जा सकता है. उदाहरण के लिए, अगर कंटेनर को उसके सामान्य साइज़ के पांचवें हिस्से तक छोटा किया गया है, तो कॉन्टेंट को पांच गुना बड़ा किया जा सकता है, ताकि कॉन्टेंट को छोटा न किया जाए. इस बारे में दो बातों का ध्यान रखें:

  1. काउंटर-ट्रांसफ़ॉर्म भी स्केल ऑपरेशन है. यह अच्छा है, क्योंकि कंटेनर पर मौजूद ऐनिमेशन की तरह ही, इसे भी तेज़ किया जा सकता है. आपको यह पक्का करना पड़ सकता है कि ऐनिमेशन वाले एलिमेंट को अपनी कंपोजिटर लेयर मिले. इससे जीपीयू को मदद मिलती है. इसके लिए, एलिमेंट में will-change: transform जोड़ें. अगर आपको पुराने ब्राउज़र के साथ काम करना है, तो backface-visiblity: hidden जोड़ें.

  2. काउंटर-ट्रांसफ़ॉर्म का हिसाब हर फ़्रेम के हिसाब से लगाया जाना चाहिए. यहां चीज़ें थोड़ी मुश्किल हो सकती हैं, क्योंकि अगर यह माना जाता है कि ऐनिमेशन सीएसएस में है और उसमें ईज़िंग फ़ंक्शन का इस्तेमाल किया जाता है, तो काउंटर-ट्रांसफ़ॉर्मेशन को ऐनिमेट करते समय, ईज़िंग को खुद काउंटर करना होगा. हालांकि, cubic-bezier(0, 0, 0.3, 1) के लिए इनवर्स कर्व का हिसाब लगाना आसान नहीं है.

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

दूसरा चरण: सीएसएस ऐनिमेशन को फ़्लाई पर बनाना

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

मुख्य फ़्रेम वाला ऐनिमेशन बनाने के लिए, हम 0 से 100 तक की वैल्यू का इस्तेमाल करते हैं. साथ ही, यह भी हिसाब लगाते हैं कि एलिमेंट और उसके कॉन्टेंट के लिए, स्केल की कौनसी वैल्यू की ज़रूरत होगी. इसके बाद, इन्हें स्ट्रिंग में बदला जा सकता है, जिसे स्टाइल एलिमेंट के तौर पर पेज में इंजेक्ट किया जा सकता है. स्टाइल इंजेक्ट करने पर, पेज पर स्टाइल फिर से कैलकुलेट करने की प्रोसेस शुरू हो जाएगी. यह ब्राउज़र के लिए अतिरिक्त काम है, लेकिन यह सिर्फ़ एक बार तब होगा, जब कॉम्पोनेंट बूट हो रहा होगा.

function createKeyframeAnimation () {
    // Figure out the size of the element when collapsed.
    let {x, y} = calculateCollapsedScale();
    let animation = '';
    let inverseAnimation = '';

    for (let step = 0; step <= 100; step++) {
    // Remap the step value to an eased one.
    let easedStep = ease(step / 100);

    // Calculate the scale of the element.
    const xScale = x + (1 - x) * easedStep;
    const yScale = y + (1 - y) * easedStep;

    animation += `${step}% {
        transform: scale(${xScale}, ${yScale});
    }`;

    // And now the inverse for the contents.
    const invXScale = 1 / xScale;
    const invYScale = 1 / yScale;
    inverseAnimation += `${step}% {
        transform: scale(${invXScale}, ${invYScale});
    }`;

    }

    return `
    @keyframes menuAnimation {
    ${animation}
    }

    @keyframes menuContentsAnimation {
    ${inverseAnimation}
    }`;
}

जिज्ञासु लोग, for-लूप में मौजूद ease() फ़ंक्शन के बारे में सोच रहे होंगे. 0 से 1 तक की वैल्यू को आसानी से मैप करने के लिए, कुछ इस तरह का फ़ंक्शन इस्तेमाल किया जा सकता है.

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

Google Search का इस्तेमाल करके, यह भी देखा जा सकता है कि यह कैसा दिखता है. बढ़िया! अगर आपको अन्य ईज़िंग ईक्वेशन की ज़रूरत है, तो Soledad Penadés का Tween.js देखें. इसमें कई ईज़िंग ईक्वेशन मौजूद हैं.

तीसरा चरण: सीएसएस ऐनिमेशन चालू करना

JavaScript में इन ऐनिमेशन को बनाने और पेज पर जोड़ने के बाद, आखिरी चरण में ऐनिमेशन चालू करने वाली क्लास को टॉगल करना होता है.

.menu--expanded {
  animation-name: menuAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

.menu__contents--expanded {
  animation-name: menuContentsAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

इससे पिछले चरण में बनाए गए ऐनिमेशन चलने लगते हैं. बेक किए गए ऐनिमेशन पहले से ही आसान कर दिए जाते हैं. इसलिए, टाइमिंग फ़ंक्शन को linear पर सेट करना ज़रूरी है. ऐसा न करने पर, हर की-फ़्रेम के बीच आसानी से बदलाव होगा, जो बहुत अजीब लगेगा!

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

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

ज़्यादा बेहतर वर्शन: सर्कुलर रीवल

इस तकनीक का इस्तेमाल, सर्कुलर आइटम को बड़ा और छोटा करने वाले ऐनिमेशन बनाने के लिए भी किया जा सकता है.

इस वर्शन के सिद्धांत, पिछले वर्शन के सिद्धांतों से काफ़ी हद तक मिलते-जुलते हैं. इसमें, किसी एलिमेंट को स्केल करने पर, उसके चाइल्ड एलिमेंट का साइज़ अपने-आप कम हो जाता है. इस मामले में, जिस एलिमेंट को बड़ा किया जा रहा है उसका border-radius 50% है, जिससे वह सर्कुलर हो जाता है. साथ ही, उसे overflow: hidden वाले किसी दूसरे एलिमेंट से रैप किया जाता है. इसका मतलब है कि आपको सर्कल, एलिमेंट के बाउंड के बाहर बड़ा नहीं दिखेगा.

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

सर्कुलर एक्सपैंशन इफ़ेक्ट का कोड, GitHub रेपो में देखा जा सकता है.

मीटिंग में सामने आए नतीजे

स्केल ट्रांसफ़ॉर्म का इस्तेमाल करके, बेहतर परफ़ॉर्म करने वाले क्लिप ऐनिमेशन बनाने का तरीका यहां बताया गया है. अगर सब कुछ ठीक-ठाक होता, तो क्लिप के एनिमेशन को तेज़ी से चलाया जा सकता था. हालांकि, clip या clip-path को एनिमेट करते समय आपको सावधानी बरतनी चाहिए. साथ ही, width या height को एनिमेट करने से पूरी तरह बचना चाहिए.

इस तरह के इफ़ेक्ट के लिए, वेब ऐनिमेशन का इस्तेमाल करना भी आसान होगा. इसकी वजह यह है कि इनमें JavaScript API होता है. हालांकि, अगर सिर्फ़ transform और opacity को ऐनिमेट किया जाता है, तो वे कंपोजिटर थ्रेड पर चल सकते हैं. माफ़ करें, वेब ऐनिमेशन के लिए सहायता बहुत अच्छी नहीं है. हालांकि, अगर वे उपलब्ध हैं, तो प्रगतिशील बेहतर बनाने की सुविधा का इस्तेमाल करके उनका इस्तेमाल किया जा सकता है.

if ('animate' in HTMLElement.prototype) {
    // Animate with Web Animations.
} else {
    // Fall back to generated CSS Animations or JS.
}

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

अगर आपको इस इफ़ेक्ट के कोड के बारे में जानना है, तो यूज़र इंटरफ़ेस (यूआई) एलिमेंट के सैंपल वाले GitHub रिपॉज़िटरी पर जाएं. साथ ही, हमेशा की तरह हमें नीचे टिप्पणियों में बताएं कि आपको यह सुविधा कैसी लगी.