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

पॉल लुईस
स्टीफ़न मैकग्रूअर
स्टीफ़न मैकग्रूर

बहुत ज़्यादा शब्द हैं, पढ़ा नहीं गया

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

परफ़ॉर्मेंस देने वाले पैरलैक्स इफ़ेक्ट और इनफ़ाइनाइट स्क्रोलर बनाने के तरीकों के बारे में अपडेट पहले ही पोस्ट किए गए थे. इस पोस्ट में हम देखेंगे कि अगर आपको परफ़ॉर्मेंट क्लिप ऐनिमेशन चाहिए, तो इसमें क्या-क्या शामिल है. अगर आपको डेमो देखना है, तो सैंपल यूज़र इंटरफ़ेस (यूआई) एलिमेंट 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 को ऐनिमेट करना ज़रूरी है. इन प्रॉपर्टी के लिए लेआउट की गणना करने और ऐनिमेशन के हर फ़्रेम पर नतीजों को पेंट करने की ज़रूरत होती है. यह बहुत महंगा हो सकता है और आम तौर पर इससे 60 FPS (फ़्रेम प्रति सेकंड) की चूक हो सकती है. अगर आपके लिए यह समाचार है, तो हमारी रेंडरिंग परफ़ॉर्मेंस से जुड़ी गाइड पढ़ें. यहां आपको रेंडरिंग की प्रोसेस के काम करने के तरीके के बारे में ज़्यादा जानकारी मिल सकती है.

खराब: सीएसएस क्लिप या क्लिप-पाथ प्रॉपर्टी का इस्तेमाल करें

width और height को ऐनिमेट करने का एक विकल्प यह हो सकता है कि 'बड़ा करें' और 'छोटा करें' इफ़ेक्ट को ऐनिमेट करने के लिए, clip प्रॉपर्टी का इस्तेमाल किया जाए. यह सुविधा अब अब सेवा में नहीं है. या अगर आप चाहें, तो clip-path का इस्तेमाल करें. हालांकि, clip-path का इस्तेमाल करना clip से कम बेहतर तरीके से काम करता है. हालांकि, 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 FPS (फ़्रेम प्रति सेकंड) तक हिट होने की संभावना होती है.

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

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

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

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

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

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

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

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

दूसरा चरण: सीएसएस ऐनिमेशन तुरंत बनाएं

इसकी शुरुआत में अजीब लग सकता है कि इसकी मदद से मुख्य-फ़्रेम वाला एक ऐनिमेशन बनाया जा सकता है, जिसमें अपने ईज़िंग फ़ंक्शन को डाइनैमिक तरीके से इस्तेमाल किया जा सकता है. इसके बाद, उसे पेज में डाला जा सकता है, ताकि मेन्यू में उसका इस्तेमाल किया जा सके. (यह बताने के लिए 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 के रेपो में मिल सकता है.

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

स्केल ट्रांसफ़ॉर्म का इस्तेमाल करके, परफ़ॉर्म करने वाले क्लिप ऐनिमेशन बनाने का तरीका. एक बेहतरीन दुनिया में, क्लिप ऐनिमेशन को तेज़ी से देखना बहुत अच्छा लगेगा (जेक आर्किबाल्ड के बनाए इसके लिए Chromium की गड़बड़ी है), लेकिन जब तक हम ऐसा नहीं कर लेते, तब तक आपको 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 रेपो पर एक नज़र डालें. हमेशा की तरह, नीचे दी गई टिप्पणियों में हमें बताएं कि आपका अनुभव कैसा रहा.