Google I/O 2016 प्रोग्रेसिव वेब ऐप्लिकेशन बनाना

आयोवा होम

खास जानकारी

जानें कि हमने वेब कॉम्पोनेंट, Polymer, और मटीरियल डिज़ाइन का इस्तेमाल करके, एक पेज वाला ऐप्लिकेशन कैसे बनाया और उसे Google.com पर प्रोडक्शन में लॉन्च किया.

नतीजे

  • नेटिव ऐप्लिकेशन के मुकाबले ज़्यादा यूज़र ऐक्टिविटी (मोबाइल वेब पर 4:06 मिनट बनाम Android पर 2:40 मिनट).
  • सेवा वर्कर कैश मेमोरी की मदद से, लौटने वाले उपयोगकर्ताओं के लिए फ़र्स्ट पेंट 450 मिलीसेकंड तक तेज़ हुआ
  • 84% वेबसाइट पर आने वाले लोगों के डिवाइस पर Service Worker काम करता है
  • साल 2015 की तुलना में, 'होम स्क्रीन पर जोड़ें' सुविधा का इस्तेमाल करके, 2016 में 900% ज़्यादा वीडियो सेव किए गए.
  • 3.8% उपयोगकर्ता ऑफ़लाइन हो गए, लेकिन 11 हज़ार पेज व्यू जनरेट होते रहे!
  • साइन इन किए हुए 50% उपयोगकर्ताओं ने सूचनाएं चालू की हैं.
  • उपयोगकर्ताओं को 5,36,000 सूचनाएं भेजी गईं (12% उपयोगकर्ता वापस आए).
  • 99% उपयोगकर्ताओं के ब्राउज़र, वेब कॉम्पोनेंट के पॉलीफ़िल के साथ काम करते हैं

खास जानकारी

इस साल, मुझे Google I/O 2016 के प्रोग्रेसिव वेब ऐप्लिकेशन पर काम करने का मौका मिला. इसे प्यार से "IOWA" नाम दिया गया है. यह मोबाइल फ़र्स्ट है और पूरी तरह से ऑफ़लाइन काम करता है. साथ ही, यह मटीरियल डिज़ाइन से काफ़ी प्रेरित है.

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

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

GitHub पर देखें

वेब कॉम्पोनेंट का इस्तेमाल करके एसपीए बनाना

हर पेज को कॉम्पोनेंट के तौर पर इस्तेमाल करना

हमारे फ़्रंटएंड का एक मुख्य पहलू यह है कि यह वेब कॉम्पोनेंट पर आधारित है. असल में, हमारे एसपीए में मौजूद हर पेज एक वेब कॉम्पोनेंट होता है:

    <io-home-page date="2016-05-18T17:00:00Z" app="[[app]]"></io-home-page>
    <io-schedule-page date="2016-05-18T17:00:00Z" app="{ % templatetag openvariable % }app}}"></io-schedule-page>
    <io-attend-page></io-attend-page>
    <io-extended-page></io-extended-page>
    <io-faq-page></io-faq-page>

हमने ऐसा क्यों किया? पहली वजह यह है कि यह कोड पढ़ा जा सकता है. पहली बार पढ़ने पर, यह साफ़ तौर पर पता चल जाता है कि हमारे ऐप्लिकेशन का हर पेज क्या है. दूसरी वजह यह है कि एसपीए बनाने के लिए, वेब कॉम्पोनेंट में कुछ अच्छी प्रॉपर्टी होती हैं. <template> एलिमेंट, कस्टम एलिमेंट, और शैडो डीओएम की खास सुविधाओं की मदद से, स्टेटस मैनेजमेंट, व्यू चालू करने, और स्टाइल को स्कोप करने जैसी कई सामान्य समस्याएं हल हो जाती हैं. ये ब्राउज़र में जोड़े जा रहे डेवलपर टूल हैं. इनका फ़ायदा क्यों न लें?

हर पेज के लिए कस्टम एलिमेंट बनाने पर, हमें बिना किसी शुल्क के ये सुविधाएं मिलीं:

  • पेज का लाइफ़साइकल मैनेजमेंट.
  • पेज के हिसाब से स्कोप की गई सीएसएस/एचटीएमएल.
  • किसी पेज के लिए खास तौर पर इस्तेमाल की जाने वाली सभी सीएसएस/एचटीएमएल/जेएस को ज़रूरत के हिसाब से बंडल किया जाता है और एक साथ लोड किया जाता है.
  • व्यू का फिर से इस्तेमाल किया जा सकता है. पेज, डीओएम नोड होते हैं. इसलिए, उन्हें जोड़ने या हटाने से व्यू बदल जाता है.
  • आने वाले समय में, हमारे ऐप्लिकेशन को मैनेज करने वाले लोग, मार्कअप को समझकर हमारे ऐप्लिकेशन को आसानी से समझ सकते हैं.
  • सर्वर से रेंडर किए गए मार्कअप को बेहतर बनाया जा सकता है. ऐसा इसलिए, क्योंकि ब्राउज़र, एलिमेंट की परिभाषाओं को रजिस्टर और अपग्रेड करता है.
  • कस्टम एलिमेंट में इनहेरिटेंस मॉडल होता है. DRY कोड अच्छा कोड होता है.
  • …और भी बहुत कुछ.

हमने IOWA में इन फ़ायदों का पूरा फ़ायदा उठाया. आइए, कुछ जानकारी के बारे में जानते हैं.

पेजों को डाइनैमिक तौर पर चालू करना

<template> एलिमेंट, ब्राउज़र का स्टैंडर्ड तरीका है, जिससे फिर से इस्तेमाल किए जा सकने वाले मार्कअप बनाए जाते हैं. <template> में दो ऐसी विशेषताएं हैं जिनका फ़ायदा एसपीए उठा सकते हैं. सबसे पहले, <template> के अंदर मौजूद कोई भी चीज़ तब तक काम नहीं करती, जब तक टेंप्लेट का कोई इंस्टेंस नहीं बनाया जाता. दूसरा, ब्राउज़र मार्कअप को पार्स करता है, लेकिन मुख्य पेज से कॉन्टेंट ऐक्सेस नहीं किया जा सकता. यह मार्कअप का एक ऐसा हिस्सा है जिसे फिर से इस्तेमाल किया जा सकता है. उदाहरण के लिए:

<template id="t">
    <div>This markup is inert and not part of the main page's DOM.</div>
    <img src="profile.png"> <!-- not loaded by the browser -->
    <video id="vid" src="vid.mp4"></video> <!-- doesn't load/start -->
    <script>alert("Not run until the template is stamped");</script>
</template>

Polymer, <template> को एक्सटेंशन टाइप के कस्टम एलिमेंट के साथ बढ़ाता है. जैसे, <template is="dom-if"> और <template is="dom-repeat">. ये दोनों कस्टम एलिमेंट हैं, जो <template> को अतिरिक्त सुविधाएं देते हैं. वेब कॉम्पोनेंट के एलान वाले तौर-तरीके की वजह से, दोनों ठीक वैसा ही काम करते हैं जैसा आपको उम्मीद होती है. पहला कॉम्पोनेंट, शर्त के आधार पर मार्कअप को स्टैंप करता है. दूसरा, किसी सूची (डेटा मॉडल) में मौजूद हर आइटम के लिए मार्कअप दोहराता है.

IOWA, इस तरह के एक्सटेंशन एलिमेंट का इस्तेमाल कैसे कर रहा है?

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

हमारा समाधान धोखाधड़ी करना था. IOWA में, हम हर पेज के एलिमेंट को <template is="dom-if"> में रैप करते हैं, ताकि उसका कॉन्टेंट पहले बूट पर लोड न हो. इसके बाद, जब टेंप्लेट का name एट्रिब्यूट यूआरएल से मेल खाता है, तब हम पेजों को चालू कर देते हैं. <lazy-pages> वेब कॉम्पोनेंट, हमारे लिए यह पूरा लॉजिक हैंडल करता है. मार्कअप कुछ ऐसा दिखता है:

<!-- Lazy pages manages the template stamping. It watches for route changes
        and sets `template.if = true` on the appropriate template. -->
<lazy-pages>
    <template is="dom-if" name="home">
    <io-home-page date="2016-05-18T17:00:00Z"></io-home-page>
    </template>

    <template is="dom-if" name="schedule">
    <io-schedule-page date="2016-05-18T17:00:00Z"></io-schedule-page>
    </template>

    <template is="dom-if" name="attend">
    <io-attend-page></io-attend-page>
    </template>
</lazy-pages>

मुझे इस बात की सबसे ज़्यादा पसंद है कि पेज लोड होने पर, हर पेज को पार्स कर लिया जाता है और वह इस्तेमाल के लिए तैयार हो जाता है. हालांकि, उसकी सीएसएस/एचटीएमएल/जेएस सिर्फ़ तब लागू होती है, जब उसके पैरंट <template> को स्टैंप किया जाता है. वेब कॉम्पोनेंट का इस्तेमाल करके, डाइनैमिक + लेज़ी व्यू.

आने वाले समय में होने वाले सुधार

पेज पहली बार लोड होने पर, हम हर पेज के लिए सभी एचटीएमएल इंपोर्ट को एक साथ लोड कर रहे हैं. एलिमेंट की परिभाषाओं को सिर्फ़ तब लेज़ी लोड करना एक बेहतर तरीका होगा, जब उनकी ज़रूरत हो. Polymer में, एचटीएमएल इंपोर्ट को एसिंक्रोनस तरीके से लोड करने के लिए भी एक अच्छा हेल्पर है:

Polymer.Base.importHref('io-home-page.html', (e) => { ... });

IOWA ऐसा नहीं करता, क्योंकि a) हम आलसी हो गए हैं और b) यह साफ़ तौर पर नहीं पता कि हमें परफ़ॉर्मेंस में कितनी बढ़ोतरी दिखती. हमारा पहला पेंट पहले से ही ~1 सेकंड का था.

पेज का लाइफ़साइकल मैनेजमेंट

कस्टम एलिमेंट एपीआई, किसी कॉम्पोनेंट के स्टेटस को मैनेज करने के लिए, "लाइफ़साइकल कॉलबैक" तय करता है. इन तरीकों को लागू करने पर, आपको कॉम्पोनेंट के लाइफ़ साइकल में फ़्री हुक मिलते हैं:

createdCallback() {
    // automatically called when an instance of the element is created.
}

attachedCallback() {
    // automatically called when the element is attached to the DOM.
}

detachedCallback() {
    // automatically called when the element is removed from the DOM.
}

attributeChangedCallback() {
    // automatically called when an HTML attribute changes.
}

IOWA में इन कॉलबैक का फ़ायदा लेना आसान था. याद रखें कि हर पेज एक अलग डीओएम नोड होता है. हमारे एसपीए में "नए व्यू" पर जाने के लिए, एक नोड को डीओएम से अटैच करना और दूसरे को हटाना होता है.

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

onPageTransitionDone() {
    // page transition animations are complete.
},

onSubpageTransitionDone() {
    // sub nav/tab page transitions are complete.
}

ये बदलाव, काम में लगने वाले समय को बढ़ाने और पेज ट्रांज़िशन के बीच होने वाली रुकावट को कम करने के लिए, काम के थे. इस विषय पर ज़्यादा जानकारी बाद में.

सभी पेजों पर सामान्य फ़ंक्शन को DRY करना

इनहेरिटेंस, कस्टम एलिमेंट की एक बेहतरीन सुविधा है. यह वेब के लिए, स्टैंडर्ड इनहेरिटेंस मॉडल उपलब्ध कराता है.

माफ़ करें, इस लेख को लिखने के समय, एलिमेंट इनहेरिटेंस की सुविधा को Polymer 1.0 में अभी तक लागू नहीं किया गया है. इस दौरान, Polymer की बहीवियर सुविधा भी उतनी ही काम की थी. व्यवहार, सिर्फ़ मिक्सिन होते हैं.

सभी पेजों पर एक ही एपीआई प्लैटफ़ॉर्म बनाने के बजाय, शेयर किए गए मिक्सिन बनाकर कोडबेस को DRY-up करना बेहतर था. उदाहरण के लिए, PageBehavior उन सामान्य प्रॉपर्टी/तरीकों के बारे में बताता है जिनकी हमारे ऐप्लिकेशन के सभी पेजों को ज़रूरत होती है:

PageBehavior.html

let PageBehavior = {

    // Common properties all pages need.
    properties: {
    name: { type: String }, // Slug name of the page.
    ...
    },

    attached() {
    // If the page defines a `onPageTransitionDone`, call it when the router
    // fires 'page-transition-done'.
    if (this.onPageTransitionDone) {
        this.listen(document.body, 'page-transition-done', 'onPageTransitionDone');
    }

    // Update page meta data when new page is navigated to.
    document.body.id = `page-${this.name}`;
    document.title = this.title || 'Google I/O 2016';

    // Scroll to top of new page.
    if (IOWA.Elements.Scroller) {
        IOWA.Elements.Scroller.scrollTop = 0;
    }

    this.setupSubnavEffects();
    },

    detached() {
    this.unlisten(document.body, 'page-transition-done', 'onPageTransitionDone');
    this.teardownSubnavEffects();
    }
};

IOWA.IOBehaviors = IOWA.IOBehaviors || {PageBehavior: PageBehavior};

जैसा कि आप देख सकते हैं, PageBehavior सामान्य टास्क करता है. ये टास्क, किसी नए पेज पर विज़िट करने पर चलते हैं. जैसे, document.title को अपडेट करना, स्क्रोल की पोज़िशन को रीसेट करना, और स्क्रोल और सब नेविगेशन इफ़ेक्ट के लिए इवेंट लिसनर सेट अप करना.

अलग-अलग पेज, PageBehavior को डिपेंडेंसी के तौर पर लोड करके और behaviors का इस्तेमाल करके, PageBehavior का इस्तेमाल करते हैं. ज़रूरत पड़ने पर, वे इसकी बुनियादी प्रॉपर्टी/तरीकों को बदल भी सकते हैं. उदाहरण के लिए, हमारे होम पेज का "सबक्लास" इन चीज़ों को बदल देता है:

io-home-page.html

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="PageBehavior.html">
<!-- rest of the import dependencies used by the page. -->

<dom-module id="io-home-page">
    <template>
    <!-- PAGE'S MARKUP -->
    </template>
    <script>
    Polymer({
        is: 'io-home-page',

        behaviors: [IOBehaviors.PageBehavior], // All pages have common functionality.

        // Pages define their own title and slug for the router.
        title: 'Schedule - Google I/O 2016',
        name: 'home',

        // The home page has custom setup work when it's added navigated to.
        // Note: PageBehavior's attached also gets called.
        attached() {
        if (this.app.isPhoneSize) {
            this.listen(IOWA.Elements.ScrollContainer, 'scroll', '_onPageScroll');
        }
        },

        // The home page does its own cleanup when a new page is navigated to.
        // Note: PageBehavior's detached also gets called.
        detached() {
        this.unlisten(IOWA.Elements.ScrollContainer, 'scroll', '_onPageScroll');
        },

        // The home page can define onPageTransitionDone to do extra work
        // when page transitions are done, and thus preventing janky animations.
        onPageTransitionDone() {
        ...
        }
    });
    </script>
</dom-module>

स्टाइल शेयर करना

अपने ऐप्लिकेशन के अलग-अलग कॉम्पोनेंट में स्टाइल शेयर करने के लिए, हमने Polymer के शेयर किए गए स्टाइल मॉड्यूल का इस्तेमाल किया. स्टाइल मॉड्यूल की मदद से, सीएसएस का एक हिस्सा एक बार तय किया जा सकता है और उसे पूरे ऐप्लिकेशन में अलग-अलग जगहों पर फिर से इस्तेमाल किया जा सकता है. हमारे लिए, "अलग-अलग जगहों" का मतलब अलग-अलग कॉम्पोनेंट से है.

IOWA में, हमने shared-app-styles बनाया है, ताकि हम अपने बनाए गए पेजों और अन्य कॉम्पोनेंट में रंग, टाइपोग्राफ़ी, और लेआउट क्लास शेयर कर सकें.

shared-app-styles.html

<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="../bower_components/iron-flex-layout/iron-flex-layout.html">
<link rel="import" href="../bower_components/paper-styles/color.html">

<dom-module id="shared-app-styles">
    <template>
    <style>
        [layout] {
        @apply(--layout);
        }
        [layout][horizontal] {
        @apply(--layout-horizontal);
        }
        .scrollable {
        @apply(--layout-scroll);
        }
        .noscroll {
        overflow: hidden;
        }
        /* Style radio buttons and tabs the same throughout the app */
        paper-tabs {
        --paper-tabs-selection-bar-color: currentcolor;
        }
        paper-radio-button {
        --paper-radio-button-checked-color: var(--paper-cyan-600);
        --paper-radio-button-checked-ink-color: var(--paper-cyan-600);
        }
        ...
    </style>
    </template>
</dom-module>

io-home-page.html

<link rel="import" href="shared-app-styles.html">
<!-- Rest of import dependencies used by the page. -->

<dom-module id="io-home-page">
    <template>
    <style include="shared-app-styles">
        :host { display: block} /* Other element styles can go here. */
    </style>
    <!-- PAGE'S MARKUP -->
    </template>
    <script>Polymer({...});</script>
</dom-module>

यहां <style include="shared-app-styles"></style>, Polymer का सिंटैक्स है, जिसका मतलब है कि "shared-app-styles" नाम के मॉड्यूल में स्टाइल शामिल करें.

ऐप्लिकेशन की स्थिति शेयर करना

अब तक आपको पता चल गया होगा कि हमारे ऐप्लिकेशन का हर पेज एक कस्टम एलिमेंट है. मैंने लाखों बार कहा है. ठीक है, लेकिन अगर हर पेज एक अलग वेब कॉम्पोनेंट है, तो हो सकता है कि आप खुद से पूछ रहे हों कि हम ऐप्लिकेशन में स्टेटस कैसे शेयर करते हैं.

IOWA, स्टेटस शेयर करने के लिए, डिपेंडेंसी इंजेक्शन (Angular) या Redux (React) जैसी तकनीक का इस्तेमाल करता है. हमने एक ग्लोबल app प्रॉपर्टी बनाई और उससे शेयर की गई सब-प्रॉपर्टी को जोड़ा. app इसे हमारे ऐप्लिकेशन में हर उस कॉम्पोनेंट में इंजेक्ट करके पास किया जाता है जिसे इसके डेटा की ज़रूरत होती है. Polymer की डेटा बाइंडिंग सुविधाओं का इस्तेमाल करके, यह काम आसानी से किया जा सकता है. ऐसा इसलिए, क्योंकि हम बिना कोई कोड लिखे वायरिंग कर सकते हैं:

<lazy-pages>
    <template is="dom-if" name="home">
    <io-home-page date="2016-05-18T17:00:00Z" app="[[app]]"></io-home-page>
    </template>

    <template is="dom-if" name="schedule">
    <io-schedule-page date="2016-05-18T17:00:00Z" app="{ % templatetag openvariable % }app}}"></io-schedule-page>
    </template>
    ...
</lazy-pages>

<google-signin client-id="..." scopes="profile email"
                            user="{ % templatetag openvariable % }app.currentUser}}"></google-signin>

<iron-media-query query="(min-width:320px) and (max-width:768px)"
                                query-matches="{ % templatetag openvariable % }app.isPhoneSize}}"></iron-media-query>

जब उपयोगकर्ता हमारे ऐप्लिकेशन में लॉग इन करते हैं, तो <google-signin> एलिमेंट अपनी user प्रॉपर्टी को अपडेट करता है. यह प्रॉपर्टी app.currentUser से बंधी होती है. इसलिए, जिस पेज को मौजूदा उपयोगकर्ता को ऐक्सेस करना है उसे सिर्फ़ app से बंधना होगा और currentUser सब-प्रॉपर्टी को पढ़ना होगा. यह तकनीक, ऐप्लिकेशन में स्टेटस शेयर करने के लिए अपने-आप काम की है. हालांकि, इसका एक और फ़ायदा यह हुआ कि हमने एक ही साइन इन एलिमेंट बनाया और साइट पर इसके नतीजों का फिर से इस्तेमाल किया. मीडिया क्वेरी के लिए भी यही बात लागू होती है. हर पेज के लिए, साइन इन की प्रक्रिया को दोहराना या मीडिया क्वेरी का अपना सेट बनाना बेकार होता. इसके बजाय, ऐप्लिकेशन के लेवल पर ऐसे कॉम्पोनेंट मौजूद होते हैं जो ऐप्लिकेशन के सभी फ़ंक्शन/डेटा के लिए ज़िम्मेदार होते हैं.

पेज ट्रांज़िशन

Google I/O वेब ऐप्लिकेशन पर नेविगेट करने पर, आपको इसके शानदार पेज ट्रांज़िशन (मटीरियल डिज़ाइन à la) दिखेंगे.

IOWA के पेज ट्रांज़िशन की सुविधा को इस्तेमाल करते हुए दिखाया गया है.
आईओवा के पेज ट्रांज़िशन की कार्रवाई.

जब उपयोगकर्ता किसी नए पेज पर जाते हैं, तो कुछ चीज़ें एक क्रम में होती हैं:

  1. सबसे ऊपर मौजूद नेविगेशन बार, नए लिंक पर एक चुनने वाला बार स्लाइड करता है.
  2. पेज की हेडिंग धीरे-धीरे गायब हो जाती है.
  3. पेज का कॉन्टेंट नीचे की ओर स्लाइड करता है और फिर धीरे-धीरे गायब हो जाता है.
  4. उन ऐनिमेशन को उलटने पर, नए पेज का हेडिंग और कॉन्टेंट दिखता है.
  5. (ज़रूरी नहीं) नया पेज, शुरू करने से जुड़ा अतिरिक्त काम करता है.

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

यह सुविधा कैसे काम करती है

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

class Router {

    init() {
    window.addEventListener('popstate', e => this.runPageTransition());
    }

    runPageTransition() {
    let endPage = this.state.end.page;

    this.fire('page-transition-start');              // 1. Let current page know it's starting.

    IOWA.PageAnimation.runExitAnimation()            // 2. Play exist animation sequence.
        .then(() => {
        IOWA.Elements.LazyPages.selected = endPage;  // 3. Activate new page in <lazy-pages>.
        this.state.current = this.parseUrl(this.state.end.href);
        })
        .then(() => IOWA.PageAnimation.runEnterAnimation())  // 4. Play entry animation sequence.
        .then(() => this.fire('page-transition-done')) // 5. Tell new page transitions are done.
        .catch(e => IOWA.Util.reportError(e));
    }

}

"बेकार चीज़ों को हटाना: सभी पेजों पर सामान्य फ़ंक्शन" सेक्शन से याद करें कि पेज, page-transition-start और page-transition-done डीओएम इवेंट के लिए सुनते हैं. अब आपको यह दिख रहा है कि ये इवेंट कहां ट्रिगर होते हैं.

हमने runEnterAnimation/runExitAnimation हेल्पर के बजाय, Web Animations API का इस्तेमाल किया है. runExitAnimation के मामले में, हम कुछ डीओएम नोड (मास्टहेड और मुख्य कॉन्टेंट एरिया) लेते हैं. साथ ही, हर ऐनिमेशन के शुरू/खत्म होने की जानकारी देते हैं. इसके बाद, दोनों को एक साथ चलाने के लिए GroupEffect बनाते हैं:

function runExitAnimation(section) {
    let main = section.querySelector('.slide-up');
    let masthead = section.querySelector('.masthead');

    let start = {transform: 'translate(0,0)', opacity: 1};
    let end = {transform: 'translate(0,-100px)', opacity: 0};
    let opts = {duration: 400, easing: 'cubic-bezier(.4, 0, .2, 1)'};
    let opts_delay = {duration: 400, delay: 200};

    return new GroupEffect([
    new KeyframeEffect(masthead, [start, end], opts),
    new KeyframeEffect(main, [{opacity: 1}, {opacity: 0}], opts_delay)
    ]);
}

व्यू ट्रांज़िशन को ज़्यादा (या कम) बेहतर बनाने के लिए, सिर्फ़ ऐरे में बदलाव करें!

स्क्रोल इफ़ेक्ट

पेज को स्क्रोल करने पर, IOWA में कुछ दिलचस्प इफ़ेक्ट दिखते हैं. पहला, हमारा फ़्लोटिंग ऐक्शन बटन (एफ़एबी), जो उपयोगकर्ताओं को पेज पर सबसे ऊपर ले जाता है:

    <a href="#" tabindex="-1" aria-hidden="true" aria-label="back to top" onclick="backToTop">
      <paper-fab icon="io:expand-less" noink tabindex="-1"></paper-fab>
    </a>

स्मूद स्क्रोल को लागू करने के लिए, Polymer के app-layout एलिमेंट का इस्तेमाल किया जाता है. ये स्क्रोलिंग के लिए, स्टिक/रिटर्निंग टॉप नेविगेशन, ड्रॉप शैडो, कलर और बैकग्राउंड ट्रांज़िशन, पैरललक्स इफ़ेक्ट, और स्मूद स्क्रोलिंग जैसे बेहतरीन इफ़ेक्ट उपलब्ध कराते हैं.

    // Smooth scrolling the back to top FAB.
    function backToTop(e) {
      e.preventDefault();

      Polymer.AppLayout.scroll({top: 0, behavior: 'smooth',
                                target: document.documentElement});

      e.target.blur();  // Kick focus back to the page so user starts from the top of the doc.
    }

हमने <app-layout> एलिमेंट का इस्तेमाल, स्टिक नेविगेशन के लिए भी किया है. जैसा कि वीडियो में देखा जा सकता है, जब उपयोगकर्ता पेज को नीचे की ओर स्क्रोल करते हैं, तो यह विज्ञापन हट जाता है. वहीं, ऊपर की ओर स्क्रोल करने पर, यह फिर से दिखने लगता है.

स्क्रोल किए जा सकने वाले नेविगेशन
का इस्तेमाल करके, स्क्रोल नेविगेशन को स्टिकी बनाएं.

हमने <app-header> एलिमेंट का इस्तेमाल, उसी तरह किया है जिस तरह वह है. ऐप्लिकेशन में इसे जोड़ना और शानदार स्क्रोलिंग इफ़ेक्ट पाना आसान था. बेशक, हम इन्हें खुद लागू कर सकते थे, लेकिन फिर से इस्तेमाल किए जा सकने वाले कॉम्पोनेंट में जानकारी पहले से कोड में होने से, हमें काफ़ी समय की बचत हुई.

एलिमेंट का एलान करें. एट्रिब्यूट की मदद से, इसे पसंद के मुताबिक बनाएं. आपका काम पूरा हुआ!

    <app-header reveals condenses effects="fade-background waterfall"></app-header>

नतीजा

I/O के प्रगतिशील वेब ऐप्लिकेशन के लिए, हमने वेब कॉम्पोनेंट और Polymer के पहले से बने मटीरियल डिज़ाइन विजेट की मदद से, कुछ हफ़्तों में पूरा फ़्रंटएंड बना लिया. नेटिव एपीआई (कस्टम एलिमेंट, शैडो डीओएम, <template>) की सुविधाएं, एसपीए के डाइनैमिक होने की वजह से अपने-आप काम करती हैं. फिर से इस्तेमाल करने से काफ़ी समय बचता है.

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