ملخّص
تعرَّف على كيفية إنشاء تطبيق مكوّن من صفحة واحدة باستخدام مكوّنات الويب وPolymer وتصميم Material Design وإطلاقه على Google.com.
النتائج
- تفاعل أكبر من التطبيق الأصلي (4:06 دقيقة على الويب للأجهزة الجوّالة مقابل 2:40 دقيقة على Android)
- تحسين سرعة عرض الصفحة الأولى للمستخدمين المكرّري الزيارة بمقدار 450 ملي ثانية بفضل ميزة التخزين المؤقت لعامل الخدمة
- 84% من الزوّار يوفّرون مكوّن Service Worker
- ارتفع عدد عمليات الحفظ من خلال ميزة "الإضافة إلى الشاشة الرئيسية" بنسبة% 900 مقارنةً بعام 2015.
- فقد انقطع الاتصال بالإنترنت لدى% 3.8 من المستخدمين، ولكنّهم واصلوا تحقيق 11 ألف مشاهدة للصفحة.
- فعّل% 50 من المستخدمين الذين سجّلوا الدخول الإشعارات.
- تم إرسال 536 ألف إشعار إلى المستخدمين (استعاد% 12 منهم التطبيقات).
- توافق 99% من متصفّحات المستخدمين مع مكونات الويب القابلة للاستبدال
نظرة عامة
هذا العام، كان من دواعي سروري العمل على تطبيق الويب التفاعلي لعام 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، يؤدي ببساطة إضافة الصفحات أو إزالتها إلى تغيير العرض.
- يمكن للمشرفين المستقبليين فهم تطبيقنا ببساطة من خلال فهم الترميز.
- يمكن تحسين الترميز الذي يعرضه الخادم تدريجيًا عندما يسجّل المتصفح تعريفات العناصر ويرقيها.
- تحتوي "العناصر المخصّصة" على نموذج اكتساب. إنّ التعليمات البرمجية غير المتكرّرة هي تعليمات جيدة.
- …والعديد من العناصر الأخرى
لقد استفدنا إلى أقصى حد من هذه المزايا في أيوا. لنطّلع على بعض التفاصيل.
تفعيل الصفحات ديناميكيًا
عنصر <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. في هذه الأثناء، كانت ميزة السلوكيات في 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، ستلاحظ مؤثرات تصميم المواد الرائعة التي تُستخدم في الانتقال بين الصفحات.
عندما ينتقل المستخدمون إلى صفحة جديدة، تحدث سلسلة من الإجراءات:
- ينقل شريط التنقّل العلوي شريط اختيار إلى الرابط الجديد.
- يتلاشى عنوان الصفحة.
- ينزلق محتوى الصفحة للأسفل ثم يختفي.
- من خلال عكس هذه الصور المتحركة، يظهر عنوان الصفحة الجديدة ومحتواها.
- (اختياري) تُجري الصفحة الجديدة عملًا إضافيًا لبدء التشغيل.
كان أحد التحديات التي واجهناها هو معرفة كيفية إنشاء هذا الانتقال السلس بدون التأثير في الأداء. نُجري الكثير من الأعمال الديناميكية، والبرامج غير الصالحة غير مرحب بها في هذه الحفلة. كان الحلّ الذي قدّمناه عبارة عن تركيبة من 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. وهي طريقة سهلة للبدء.