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

एरिक बिडेलमैन

आयोवा होम पेज

खास जानकारी

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

नतीजे

  • खास ऐप्लिकेशन के मुकाबले ज़्यादा जुड़ाव (4:06 मिनट मोबाइल वेब बनाम Android के 2:40 मिनट).
  • सर्विस वर्कर कैशिंग की वजह से, लौटने वाले उपयोगकर्ताओं के लिए 450 मिलीसेकंड तेज़ फ़र्स्ट पेंट
  • 84% विज़िटर ने सर्विस वर्कर की मदद की
  • साल 2015 के मुकाबले, वीडियो को होमस्क्रीन पर सेव करने की संख्या में 900% से ज़्यादा की बढ़ोतरी हुई.
  • 3.8% उपयोगकर्ता ऑफ़लाइन हो गए, लेकिन 11 हज़ार पेज व्यू जनरेट हो रहे हैं!
  • साइन इन किए हुए 50% उपयोगकर्ताओं ने सूचनाएं पाने की सुविधा चालू की है.
  • उपयोगकर्ताओं को 5.36 लाख सूचनाएं भेजी गईं (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> एलिमेंट, कस्टम एलिमेंट, और शैडो डीओएम की सुविधाओं की वजह से कई सामान्य परेशानियां दूर हो जाती हैं. जैसे, स्टेट मैनेजमेंट, व्यू ऐक्टिवेशन, स्टाइल का दायरा बढ़ाना. ये डेवलपर टूल हैं, जो ब्राउज़र में बनाए जा रहे हैं. क्यों न इनका फ़ायदा लिया जाए?

हर पेज के लिए एक कस्टम एलिमेंट बनाने से, हमें बहुत कुछ मुफ़्त मिला:

  • पेज लाइफ़साइकल मैनेजमेंट.
  • पेज के लिए स्कोप किया गया सीएसएस/एचटीएमएल.
  • किसी पेज के लिए मौजूद सभी सीएसएस/एचटीएमएल/जेएस को ज़रूरत के मुताबिक बंडल करके एक साथ लोड किया जाता है.
  • व्यू फिर से इस्तेमाल किए जा सकते हैं. पेज DOM नोड होते हैं, इसलिए उन्हें जोड़ने या हटाने से व्यू में बदलाव होता है.
  • आने वाले समय में मैनेज करने वाले लोग, मार्कअप को देखकर हमारे ऐप्लिकेशन को समझ सकते हैं.
  • सर्वर से रेंडर किए गए मार्कअप को धीरे-धीरे बेहतर बनाया जा सकता है, क्योंकि एलिमेंट की परिभाषाएं ब्राउज़र में रजिस्टर और अपग्रेड होती हैं.
  • कस्टम एलिमेंट में इनहेरिटेंस मॉडल होता है. 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" autoplay></video> <!-- doesn't load/start -->
    <script>alert("Not run until the template is stamped");</script>
</template>

पॉलीमर कुछ टाइप एक्सटेंशन कस्टम एलिमेंट की मदद से, <template> का extends देता है, जैसे कि <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>

इसकी खास बात यह है कि हर पेज को पार्स कर लिया जाता है और पेज लोड होने पर वह इस्तेमाल के लिए तैयार हो जाता है. हालांकि, इसके CSS/HTML/JS को सिर्फ़ मांग पर लागू किया जाता है (जब इसके पैरंट <template> पर स्टैंप लगा हो). वेब कॉम्पोनेंट FTW का इस्तेमाल करके, डाइनैमिक और लेज़ी व्यू.

आने वाले समय में किए जाने वाले सुधार

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

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

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

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

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

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 में इन कॉलबैक का फ़ायदा लेना आसान था. याद रखें कि हर पेज एक सेल्फ़-कंटेन्ड DOM नोड होता है. अपने एसपीए में "नए व्यू" पर जाने के लिए, आपको डीओएम में एक नोड जोड़ना होता है और दूसरे को हटाना होता है.

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

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

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

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

सभी पेजों पर सामान्य सुविधाओं को इस्तेमाल के लिए उपलब्ध कराना

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

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

सभी पेजों पर एक जैसी एपीआई का प्लैटफ़ॉर्म बनाने के बजाय, शेयर किए गए मिक्स इन बनाकर कोडबेस को DRY-अप करना बेहतर था. उदाहरण के लिए, 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 का इस्तेमाल करके, इसका इस्तेमाल करते हैं. ज़रूरत पड़ने पर, वे अपनी मूल प्रॉपर्टी/तरीकों को भी बदल सकते हैं. उदाहरण के लिए, यहां बताया गया है कि हमारी होम पेज "सब-क्लास" में क्या बदलाव होता है:

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> है. इसका मतलब है कि "शेयर किए गए ऐप्लिकेशन-स्टाइल" नाम के मॉड्यूल में स्टाइल शामिल करें.

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

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

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

IOWA का पेज, कार्रवाई में बदल रहा है.
IOWA के पेज का ट्रांज़िशन हो रहा है.

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

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

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

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

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

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

    // 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 के साथ PWA बनाने के लिए कॉम्पोनेंट, टूल, और टेंप्लेट का एक कलेक्शन है. यह तैयार होने और काम शुरू करने का आसान तरीका है.