ملخّص
تعرّف على كيفية إنشاء تطبيق من صفحة واحدة باستخدام مكوّنات الويب والبوليمر والتصميم المتعدد الأبعاد وإطلاقه للإنتاج على Google.com.
النتائج
- تفاعل أكبر من التطبيق الأصلي (4:06 دقائق ويب على الأجهزة الجوّالة مقابل 2:40 دقيقة على Android).
- تحسين سرعة عرض الصفحة الأولى للمستخدمين المكرّري الزيارة بمقدار 450 ملي ثانية بفضل ميزة التخزين المؤقت لعامل الخدمة
- 84% من الزوّار يستخدمون Service Worker
- ارتفع عدد عمليات الحفظ من خلال ميزة "الإضافة إلى الشاشة الرئيسية" بنسبة %900 مقارنةً بعام 2015.
- انقطع اتصال 3.8% من المستخدمين بالإنترنت، إلا أنهم استمروا في تحقيق 11 ألف مشاهدة للصفحات على الويب!
- فعّل %50 من المستخدمين الذين سجّلوا الدخول الإشعارات.
- تم إرسال 536 ألف إشعار إلى المستخدمين (استعاد %12 منهم التطبيقات).
- تتوافق نسبة 99% من متصفحات المستخدمين مع رموز polyfill لمكوّنات الويب.
نظرة عامة
هذا العام، كان من دواعي سروري العمل على تطبيق الويب التفاعلي لعام 2016 في Google I/O، والذي يحمل الاسم المعرِّف "IOWA". تم تصميمها للأجهزة الجوّالة أولاً، وهي تعمل بالكامل بلا اتصال بالإنترنت، واستوحينا تصميمها بشكل كبير من تصميم المواد.
IOWA هو تطبيق صفحة واحدة (SPA) تم إنشاؤه باستخدام مكونات الويب وPolymer وFirebase، ويحتوي على خلفية واسعة النطاق مكتوبة بلغة App Engine (Go). ويعمل هذا الإطار على تخزين المحتوى مؤقتًا مسبقًا باستخدام عامل خدمة، وتحميل صفحات جديدة ديناميكيًا، والانتقال بسلاسة بين طرق العرض، وإعادة استخدام المحتوى بعد التحميل الأول.
في دراسة الحالة هذه، سأوضّح بعض القرارات المعمارية الأكثر إثارة للاهتمام التي اتّخذناها بشأن واجهة المستخدم. إذا كنت مهتمًا برمز المصدر، يمكنك الاطّلاع عليه على GitHub.
إنشاء تطبيق متعدّد الصفحات (SPA) باستخدام مكوّنات الويب
كل صفحة كمكوّن
إنّ أحد الجوانب الأساسية في الواجهة الأمامية هي أنّها تركّز على مكوّنات الويب. في الواقع، كل صفحة في تطبيقنا المتعدّد الصفحات هي عنصر ويب:
<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>
والعناصر المخصّصة وShadow DOM. هذه هي أدوات المطوّرين التي يتم تضمينها في المتصفّح. لماذا لا تستفيد منها؟
ومن خلال إنشاء عنصر مخصص لكل صفحة، حصلنا على الكثير مجانًا:
- إدارة مراحل نشاط الصفحة
- CSS/HTML مضبوطة على نطاق خاص بالصفحة.
- يتم تجميع كل ملفات CSS/HTML/JS الخاصة بصفحة معيّنة وتحميلها معًا حسب الحاجة.
- يمكن إعادة استخدام المشاهدات. نظرًا لأن الصفحات عبارة عن عُقد DOM، فإن إضافتها أو إزالتها تؤدي إلى تغيير طريقة العرض.
- يمكن للمشرفين المستقبليين فهم تطبيقنا ببساطة من خلال فهم الترميز.
- يمكن تحسين الترميز المعروض من خلال الخادم تدريجيًا، لأنّه يتم تسجيل تعريفات العناصر وترقيتها من خلال المتصفّح.
- تحتوي "العناصر المخصّصة" على نموذج اكتساب. إنّ التعليمات البرمجية التي لا تتضمّن تكرارًا هي تعليمات جيدة.
- …والعديد من العناصر الأخرى
استفدنا بشكل كامل من هذه المزايا في IOWA. لنتعمق في بعض التفاصيل.
تفعيل الصفحات ديناميكيًا
عنصر <template>
هو الطريقة العادية للمتصفّح لإنشاء ترميز قابل لإعادة الاستخدام. <template>
لها سمتان
يمكن لمنتجعات SPA الاستفادة منهما. أولاً، أي عنصر داخل
<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>
إمكانات إضافية. وبفضل
الطبيعة التعريفية لمكوّنات الويب، يؤدي كلاهما ما تتوقّعه بالضبط.
يختم المكوّن الأول العلامات استنادًا إلى شرط. ويكرّر الجزء الثاني markup لكل عنصر في قائمة (نموذج البيانات).
كيف تستخدم IOWA عناصر إضافة النوع هذه؟
إذا كنت تتذكر، كل صفحة في IOWA هي عنصر ويب. ومع ذلك، سيكون من السخيف الإعلان عن كل مكوّن عند التحميل الأول. ويعني ذلك إنشاء مثيل لكل صفحة عند تحميل التطبيق لأول مرة. لم نكن نريد التأثير سلبًا في أداء التحميل الأولي، خاصةً أنّ بعض المستخدمين سينتقلون إلى صفحة واحدة أو صفحتَين فقط.
كان الحلّ هو الغش. في IOWA، نلفّ عنصر كل صفحة في <template is="dom-if">
كي لا يتم تحميل محتوياته عند التشغيل الأول. بعد ذلك، نفعّل الصفحات عندما تتطابق سمة name
للنموذج مع عنوان URL. يتعامل مكوّن الويب <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>
). طرق العرض الديناميكية + طرق العرض غير المُحمَّلة مسبقًا باستخدام مكوّنات الويب
التحسينات المستقبلية
عند تحميل الصفحة لأول مرة، يتم تحميل جميع عمليات استيراد HTML لكل صفحة في آنٍ واحد. ويتمثّل التحسين الواضح في التحميل الكسول لتعريفات العناصر فقط عند الحاجة إليها. يتضمّن Polymer أيضًا أداة مساعدة رائعة لتحميل HTML Imports بشكل غير متزامن:
Polymer.Base.importHref('io-home-page.html', (e) => { ... });
لا تفعل IOWA ذلك لأنّنا (أ) كنا كسولين و(ب) لم يكن من الواضح مقدار التحسين الذي كان سيحدث في الأداء. كان أول رسم لدينا بالفعل حوالي ثانية واحدة.
إدارة مراحل نشاط الصفحة
تحدد واجهة برمجة التطبيقات 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 مستقلة. إنّ الانتقال إلى "عرض جديد" في تطبيق SPA هو عملية ربط عقدة واحدة بـ DOM وإزالة عقدة أخرى.
استخدمنا attachedCallback
لتنفيذ عملية الإعداد (حالة البدء وإرفاق أدوات معالجة الأحداث). عندما ينتقل المستخدمون إلى صفحة مختلفة، يُجري detachedCallback
عملية تنظيف (إزالة المستمعين وإعادة ضبط الحالة المشتركة). لقد وسّعنا أيضًا طلبات الاستدعاء الأصلية لدورة الحياة من خلال إضافة العديد من الطلبات التي أنشأناها:
onPageTransitionDone() {
// page transition animations are complete.
},
onSubpageTransitionDone() {
// sub nav/tab page transitions are complete.
}
كانت هذه الإضافات مفيدة لتأخير العمل وتقليل الارتباك بين عمليات انتقال الصفحة. سنقدّم المزيد من المعلومات حول هذا الموضوع لاحقًا.
تنفيذ الوظائف الشائعة في الصفحات
يعد الاكتساب ميزة فعالة للعناصر المخصصة. ويقدّم نموذجًا عاديًا للاكتساب على الويب.
للأسف، لم ينفذ البوليمر 1.0 بعد اكتساب العناصر في وقت الكتابة. في هذه الأثناء، كانت ميزة السلوكيات في Polymer مفيدة بالقدر نفسه. السلوكيات هي مجرد خلطات.
بدلاً من إنشاء واجهة برمجة التطبيقات نفسها في جميع الصفحات، كان من المنطقي تقليل تكرار المحتوى في ملف codebase من خلال إنشاء مكونات مساعدة مشتركة. على سبيل المثال، تحدِّد 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. تتيح لك وحدات الأنماط تحديد جزء من CSS مرة واحدة وإعادة استخدامه في مواضع مختلفة في كل تطبيق. بالنسبة إلينا، تعني "أماكن مختلفة" عناصر مختلفة.
في 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 material design).
عندما ينتقل المستخدِمون إلى صفحة جديدة، تحدث سلسلة من الإجراءات:
- ينقل شريط التنقّل العلوي شريط اختيار إلى الرابط الجديد.
- يتلاشى عنوان الصفحة.
- ينزلق محتوى الصفحة للأسفل ثم يختفي.
- عن طريق عكس هذه الصور المتحركة، يظهر عنوان الصفحة الجديدة ومحتواها.
- (اختياري) تُجري الصفحة الجديدة عملًا إضافيًا لبدء التشغيل.
كان أحد التحديات التي واجهناها هو معرفة كيفية إنشاء هذا الانتقال السلس بدون التأثير في الأداء. نُجري الكثير من الأعمال الديناميكية، والمحتوى غير المرغوب فيه غير مرحب به في هذه الحفلة. كان الحلّ الذي قدّمناه عبارة عن تركيبة من Web Animations API وPromises. من خلال استخدام هذين المكوّنين معًا، حصلنا على مرونة ونظام صور متحركة سهل الاستخدام وإمكانية التحكّم بشكل دقيق لتقليل الارتباك في das.
طريقة العمل
عندما ينقر المستخدمون على صفحة جديدة (أو يضغطون على زر الرجوع/التقديم)، ينفِّذ runPageTransition()
في جهاز التوجيه سحره من خلال تنفيذ سلسلة من التعهدات. من خلال استخدام وعد JavaScript، تمكّنا من تنسيق الصور المتحركة بعناية وساعدنا في تبسيط "عدم التزامن" في صور CSS المتحركة وتحميل المحتوى ديناميكيًا.
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
DOM. يمكنك الآن الاطّلاع على مكان بدء هذه الأحداث.
لقد استخدمنا Web Animations API بدلاً من أدوات المساعدة runEnterAnimation
/runExitAnimation
. في حالة 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 بعض التأثيرات المثيرة للاهتمام عند الانتقال إلى أعلى الصفحة أو أسفلها. الأول هو زر الإجراء الرئيسي (FAB) الذي ينقل المستخدمين إلى أعلى الصفحة:
<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. تتوافق ميزات واجهات برمجة التطبيقات الأصلية (Custom Elements وShadow DOM و<template>
) بشكلٍ طبيعي مع الديناميكية في التطبيقات المُنشأة باستخدام إطار عمل SPA. توفّر إمكانية إعادة الاستخدام الكثير من الوقت.
إذا كنت مهتمًا بإنشاء تطبيق ويب تقدّمي، يمكنك الاطّلاع على مجموعة أدوات التطبيق. "صندوق أدوات التطبيقات" في Polymer هو مجموعة من المكوّنات والأدوات والنماذج لإنشاء تطبيقات ويب تقدّمية باستخدام Polymer. وهي طريقة سهلة للبدء.