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

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 प्रॉपर्टी का इस्तेमाल करना है, तो यह ज़रूरी है कि जिस एलिमेंट पर यह काम कर रहा है वह एलिमेंट पूरी तरह से या तय जगह पर हो. इसके लिए, आपको थोड़ी मेहनत करनी पड़ सकती है.

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

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

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

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

स्केल ऐनिमेशन का इस्तेमाल करने वाले तरीके में, सबसे पहले उन एलिमेंट को पढ़ना होता है जिनसे आपको यह पता चलता है कि मेन्यू को छोटा करने और बड़ा करने पर, उसका साइज़ क्या होना चाहिए. ऐसा हो सकता है कि कुछ मामलों में, आपको एक साथ यह दोनों जानकारी न मिले. साथ ही, आपको कॉम्पोनेंट की अलग-अलग स्थितियों को पढ़ने के लिए, कुछ क्लास को टॉगल करना पड़े. हालांकि, अगर आपको ऐसा करना है, तो सावधान रहें: 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}
    }`;
}

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

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

Google Search का इस्तेमाल करके, यह भी देखा जा सकता है कि यह कैसा दिखता है. बढ़िया! अगर आपको ईज़िंग के दूसरे इक्वेशन चाहिए, तो Soedad 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 रिपॉज़िटरी पर जाएं. साथ ही, हमेशा की तरह हमें नीचे टिप्पणियों में बताएं कि आपको यह सुविधा कैसी लगी.