تحسين تدفق المستخدم عبر انتقالات الصفحة

نشرت: 2022-03-10
ملخص سريع ↬ في أي وقت يتم فيه مقاطعة تجربة المستخدم ، تزداد فرصة مغادرته. غالبًا ما يتسبب التغيير من صفحة إلى أخرى في حدوث هذا الانقطاع عن طريق إظهار وميض أبيض بدون محتوى ، أو عن طريق أخذ وقت طويل جدًا للتحميل أو إخراج المستخدم من السياق الذي كان موجودًا فيه قبل فتح الصفحة الجديدة.

يمكن أن تعزز الانتقالات بين الصفحات التجربة من خلال الاحتفاظ بسياق المستخدم (أو حتى تحسينه) ، والحفاظ على انتباهه ، وتوفير الاستمرارية البصرية والتعليقات الإيجابية. في الوقت نفسه ، يمكن أن تكون انتقالات الصفحة ممتعة وممتعة من الناحية الجمالية ويمكن أن تعزز العلامة التجارية عند إجرائها بشكل جيد.

Page Transitions

في هذه المقالة ، سننشئ ، خطوة بخطوة ، انتقالًا بين الصفحات. سنتحدث أيضًا عن إيجابيات وسلبيات هذه التقنية وكيفية دفعها إلى أقصى حدودها.

أمثلة

تستفيد العديد من تطبيقات الأجهزة المحمولة بشكل جيد من الانتقالات بين طرق العرض. في المثال أدناه ، والذي يتبع إرشادات تصميم المواد من Google ، نرى كيف تنقل الرسوم المتحركة العلاقات الهرمية والمكانية بين الصفحات.

لماذا لا نستخدم نفس الأسلوب مع مواقعنا الإلكترونية؟ لماذا نحن على ما يرام مع شعور المستخدم بأنه يتم نقله آنيًا في كل مرة تتغير فيها الصفحة؟

المزيد بعد القفز! أكمل القراءة أدناه ↓

كيفية الانتقال بين صفحات الويب

أطر SPA

قبل أن نتسخ أيدينا ، يجب أن أقول شيئًا عن أطر التطبيقات ذات الصفحة الواحدة (SPA). إذا كنت تستخدم إطار عمل SPA (مثل AngularJS أو Backbone.js أو Ember) ، فسيكون إنشاء انتقالات بين الصفحات أسهل بكثير لأن JavaScript يتم التعامل معه بالفعل. يرجى الرجوع إلى الوثائق ذات الصلة لمعرفة كيفية نقل الصفحات باستخدام إطار العمل الذي تختاره ، لأنه ربما تكون هناك بعض الأمثلة الجيدة والبرامج التعليمية.

الطريق الخاطئ

بدت محاولتي الأولى لإنشاء انتقال بين الصفحات كما يلي:

 document.addEventListener('DOMContentLoaded', function() { // Animate in }); document.addEventListener('beforeunload', function() { // Animate out });

المفهوم بسيط: استخدم رسمًا متحركًا واحدًا عندما يغادر المستخدم الصفحة ، وحركة أخرى عند تحميل الصفحة الجديدة.

ومع ذلك ، سرعان ما وجدت أن هذا الحل به بعض القيود:

  • لا نعرف المدة التي سيستغرقها تحميل الصفحة التالية ، لذلك قد لا تبدو الرسوم المتحركة مرنة.
  • لا يمكننا إنشاء انتقالات تجمع بين محتوى من الصفحات السابقة والتالية.

في الواقع ، الطريقة الوحيدة لتحقيق انتقال سلس وسلس هي التحكم الكامل في عملية تغيير الصفحة ، وبالتالي عدم تغيير الصفحة على الإطلاق . وبالتالي ، يتعين علينا تغيير نهجنا في التعامل مع المشكلة.

الطريق الصحيح

لنلقِ نظرة على الخطوات المتضمنة في إنشاء انتقال بسيط بين الصفحات بالطريقة الصحيحة. إنه يتضمن شيئًا يسمى pushState AJAX (أو PJAX) ، والذي سيحول موقع الويب الخاص بنا بشكل أساسي إلى نوع من مواقع الويب المكونة من صفحة واحدة.

لا تحقق هذه التقنية انتقالات سلسة وممتعة فحسب ، بل سنستفيد من المزايا الأخرى التي سنغطيها بالتفصيل لاحقًا في هذه المقالة.

منع سلوك الارتباط الافتراضي

تتمثل الخطوة الأولى في إنشاء مستمع حدث click لجميع الروابط لاستخدامه ، مما يمنع المتصفح من أداء سلوكه الافتراضي وتخصيص الطريقة التي يتعامل بها مع تغييرات الصفحة.

 // Note, we are purposely binding our listener on the document object // so that we can intercept any anchors added in future. document.addEventListener('click', function(e) { var el = e.target; // Go up in the nodelist until we find a node with .href (HTMLAnchorElement) while (el && !el.href) { el = el.parentNode; } if (el) { e.preventDefault(); return; } });

هذه الطريقة لإضافة مستمع حدث إلى عنصر أصلي ، بدلاً من إضافته إلى كل عقدة محددة ، تسمى تفويض الحدث ، وهي ممكنة بسبب الطبيعة الفقاعية للحدث لواجهة برمجة تطبيقات HTML DOM.

إحضار الصفحة

الآن بعد أن قطعنا المتصفح عندما يحاول تغيير الصفحة ، يمكننا جلب هذه الصفحة يدويًا باستخدام Fetch API. لنلقِ نظرة على الوظيفة التالية ، التي تجلب محتوى HTML للصفحة عند إعطاء عنوان URL الخاص بها.

 function loadPage(url) { return fetch(url, { method: 'GET' }).then(function(response) { return response.text(); }); }

بالنسبة إلى المتصفحات التي لا تدعم Fetch API ، ضع في اعتبارك إضافة polyfill أو استخدام XMLHttpRequest القديم الطراز.

قم بتغيير عنوان URL الحالي

يحتوي HTML5 على واجهة برمجة تطبيقات رائعة تسمى pushState ، والتي تسمح لمواقع الويب بالوصول إلى محفوظات المتصفح وتعديلها دون تحميل أي صفحات. أدناه ، نستخدمه لتعديل عنوان URL الحالي ليكون عنوان URL للصفحة التالية. لاحظ أن هذا تعديل لمعالج حدث النقر المرساة المعلن مسبقًا.

 if (el) { e.preventDefault(); history.pushState(null, null, el.href); changePage(); return; }

كما لاحظت ، فقد أضفنا أيضًا استدعاءًا إلى وظيفة تسمى changePage ، والتي سننظر فيها بالتفصيل قريبًا. سيتم أيضًا استدعاء الوظيفة نفسها في حدث popstate ، والذي يتم تشغيله عندما يتغير إدخال السجل النشط للمتصفح (كما هو الحال عندما ينقر المستخدم على زر الرجوع في متصفحه):

 window.addEventListener('popstate', changePage);

مع كل هذا ، نقوم بشكل أساسي ببناء نظام توجيه بدائي للغاية ، حيث لدينا أوضاع نشطة وسلبية.

يكون وضعنا النشط قيد الاستخدام عندما ينقر المستخدم على رابط ونغير عنوان URL باستخدام pushState ، بينما يكون الوضع الخامل قيد الاستخدام عندما يتغير عنوان URL ويتم إخطارنا من خلال حدث popstate . في كلتا الحالتين ، سنقوم باستدعاء changePage ، والتي تهتم بقراءة عنوان URL الجديد وتحميل الصفحة ذات الصلة.

تحليل وإضافة المحتوى الجديد

عادةً ما تحتوي الصفحات التي يتم التنقل فيها على عناصر مشتركة ، مثل header footer . لنفترض أننا نستخدم بنية DOM التالية في جميع صفحاتنا (والتي هي في الواقع بنية Smashing Magazine نفسها):

تحريك!

عندما ينقر المستخدم فوق ارتباط ، تقوم وظيفة changePage بجلب HTML لتلك الصفحة ، ثم تستخرج حاوية cc وتضيفها إلى العنصر main . في هذه المرحلة ، لدينا cc على صفحتنا ، الأولى تنتمي إلى الصفحة السابقة والثانية من الصفحة التالية.

الوظيفة التالية ، animate ، تهتم بالتلاشي المتقاطع للحاويتين من خلال تداخلهما ، وتلاشي الوعاء القديم ، والتلاشي في الوعاء الجديد وإزالة الحاوية القديمة. في هذا المثال ، أستخدم Web Animations API لإنشاء الرسوم المتحركة المتلاشية ، ولكن بالطبع يمكنك استخدام أي تقنية أو مكتبة تريدها.

 function animate(oldContent, newContent) { oldContent.style.position = 'absolute'; var fadeOut = oldContent.animate({ opacity: [1, 0] }, 1000); var fadeIn = newContent.animate({ opacity: [0, 1] }, 1000); fadeIn.onfinish = function() { oldContent.parentNode.removeChild(oldContent); }; }

الكود النهائي متاح على جيثب.

وهذه هي أساسيات نقل صفحات الويب!

المحاذير والقيود

المثال الصغير الذي أنشأناه للتو بعيد عن الكمال. في الواقع ، ما زلنا لم نأخذ في الاعتبار بعض الأشياء:

  • تأكد من أننا نؤثر على الروابط الصحيحة.
    قبل تغيير سلوك الارتباط ، يجب أن نضيف فحصًا للتأكد من أنه يجب تغييره. على سبيل المثال ، يجب أن نتجاهل جميع الروابط التي تحتوي على target="_blank" (والتي تفتح الصفحة في علامة تبويب جديدة) ، وجميع الروابط إلى المجالات الخارجية ، وبعض الحالات الخاصة الأخرى ، مثل Control/Command + click (والذي يفتح أيضًا الصفحة في علامة تبويب جديدة).
  • تحديث العناصر خارج حاوية المحتوى الرئيسية.
    حاليًا ، عندما تتغير الصفحة ، تظل جميع العناصر الموجودة خارج حاوية cc كما هي. ومع ذلك ، قد تحتاج بعض هذه العناصر إلى التغيير (والذي يمكن الآن القيام به يدويًا فقط) ، بما في ذلك title المستند ، وعنصر القائمة مع الفصل active ، وربما العديد من العناصر الأخرى اعتمادًا على موقع الويب.
  • إدارة دورة حياة JavaScript.
    تتصرف صفحتنا الآن مثل SPA ، حيث لا يغير المتصفح الصفحات نفسها. لذلك ، نحتاج إلى الاهتمام بدورة حياة JavaScript يدويًا - على سبيل المثال ، ربط أحداث معينة وإلغاء ربطها ، وإعادة تقييم المكونات الإضافية ، بما في ذلك polyfills وكود الطرف الثالث.

دعم المتصفح

المطلب الوحيد لهذا الوضع من التنقل الذي نطبقه هو pushState API ، وهو متاح في جميع المتصفحات الحديثة. تعمل هذه التقنية بشكل كامل كتعزيز تدريجي . لا يزال يتم عرض الصفحات ويمكن الوصول إليها بالطريقة المعتادة ، وسيستمر موقع الويب في العمل بشكل طبيعي عند تعطيل JavaScript.

إذا كنت تستخدم إطار عمل SPA ، ففكر في استخدام التنقل PJAX بدلاً من ذلك ، فقط للحفاظ على التنقل بسرعة. عند القيام بذلك ، تحصل على دعم قديم وتنشئ موقعًا أكثر ملاءمة لتحسين محركات البحث.

الذهاب أبعد من ذلك

يمكننا الاستمرار في دفع الحد الأقصى لهذه التقنية من خلال تحسين جوانب معينة منها. ستعمل الحيل القليلة التالية على تسريع التنقل ، مما سيعزز تجربة المستخدم بشكل كبير.

باستخدام ذاكرة التخزين المؤقت

من خلال التغيير الطفيف لوظيفة loadPage بنا ، يمكننا إضافة ذاكرة تخزين مؤقت بسيطة ، والتي تتأكد من عدم إعادة تحميل الصفحات التي تمت زيارتها بالفعل.

 var cache = {}; function loadPage(url) { if (cache[url]) { return new Promise(function(resolve) { resolve(cache[url]); }); } return fetch(url, { method: 'GET' }).then(function(response) { cache[url] = response.text(); return cache[url]; }); }

كما قد تكون خمنت ، يمكننا استخدام ذاكرة تخزين مؤقت أكثر ديمومة مع واجهة برمجة تطبيقات ذاكرة التخزين المؤقت أو ذاكرة تخزين مؤقت أخرى للتخزين الدائم من جانب العميل (مثل IndexedDB).

تحريك الصفحة الحالية

يتطلب تأثير التلاشي المتداخل لدينا تحميل الصفحة التالية وتجهيزها قبل اكتمال الانتقال. مع تأثير آخر ، قد نرغب في بدء تحريك الصفحة القديمة بمجرد نقر المستخدم على الرابط ، مما يمنح المستخدم ملاحظات فورية ، وهو مساعدة كبيرة للأداء المتصور.

باستخدام الوعود ، يصبح التعامل مع هذا النوع من المواقف أمرًا سهلاً للغاية. تخلق طريقة .all وعدًا جديدًا يتم حله بمجرد حل جميع الوعود المدرجة كحجج.

 // As soon as animateOut() and loadPage() are resolved… Promise.all[animateOut(), loadPage(url)] .then(function(values) { …

الجلب المسبق للصفحة التالية

باستخدام التنقل PJAX فقط ، عادة ما تكون تغييرات الصفحة أسرع مرتين من التنقل الافتراضي ، لأن المتصفح لا يحتاج إلى تحليل وتقييم أي نصوص أو أنماط على الصفحة الجديدة.

ومع ذلك ، يمكننا المضي قدمًا عن طريق البدء في التحميل المسبق للصفحة التالية عندما يحوم المستخدم فوق الرابط أو يبدأ في لمسه.

كما ترى ، عادة ما يكون هناك تأخير من 200 إلى 300 مللي ثانية في تحوم المستخدم والنقر. هذا هو الوقت الميت وعادة ما يكون كافيا لتحميل الصفحة التالية.

ومع ذلك ، يجب الجلب المسبق بحكمة لأنه يمكن أن يصبح بسهولة عنق الزجاجة. على سبيل المثال ، إذا كانت لديك قائمة طويلة من الروابط وكان المستخدم يتصفحها ، فستقوم هذه التقنية بجلب جميع الصفحات مسبقًا لأن الروابط تمر تحت الماوس.

هناك عامل آخر يمكننا اكتشافه وأخذه في الاعتبار عند تحديد ما إذا كان سيتم الجلب المسبق وهو سرعة اتصال المستخدم. (ربما يصبح هذا ممكنًا في المستقبل باستخدام واجهة برمجة تطبيقات معلومات الشبكة.)

الإخراج الجزئي

في وظيفة loadPage بنا ، نقوم بإحضار مستند HTML بأكمله ، لكننا في الواقع نحتاج فقط إلى حاوية cc . إذا كنا نستخدم لغة من جانب الخادم ، فيمكننا اكتشاف ما إذا كان الطلب يأتي من مكالمة AJAX مخصصة معينة ، وإذا كان الأمر كذلك ، فقم بإخراج الحاوية التي يحتاجها فقط. باستخدام Headers API ، يمكننا إرسال رأس HTTP مخصص في طلب الجلب الخاص بنا.

 function loadPage(url) { var myHeaders = new Headers(); myHeaders.append('x-pjax', 'yes'); return fetch(url, { method: 'GET', headers: myHeaders, }).then(function(response) { return response.text(); }); }

بعد ذلك ، على جانب الخادم (باستخدام PHP في هذه الحالة) ، يمكننا اكتشاف ما إذا كان رأسنا المخصص موجودًا قبل إخراج الحاوية المطلوبة فقط:

 if (isset($_SERVER['HTTP_X_PJAX'])) { // Output just the container }

سيؤدي ذلك إلى تقليل حجم رسالة HTTP وأيضًا تقليل التحميل من جانب الخادم.

تغليف

بعد تنفيذ هذه التقنية في مشروعين ، أدركت أن المكتبة القابلة لإعادة الاستخدام ستكون مفيدة للغاية. سيوفر لي الوقت في تنفيذه في كل مناسبة ، مما يتيح لي التركيز على تأثيرات الانتقال نفسها.

وهكذا ولدت Barba.js ، مكتبة صغيرة (4 كيلوبايت مصغرة و gZip'd) تلخص كل هذا التعقيد وتوفر واجهة برمجة تطبيقات لطيفة ونظيفة وبسيطة للمطورين لاستخدامها. كما أنها تمثل طرق العرض وتأتي مع انتقالات قابلة لإعادة الاستخدام والتخزين المؤقت والجلب المسبق والأحداث. إنه مفتوح المصدر ومتوفر على جيثب.

خاتمة

لقد رأينا الآن كيفية إنشاء تأثير التلاشي وإيجابيات وسلبيات استخدام التنقل PJAX لتحويل موقعنا على الويب بشكل فعال إلى منتجع صحي. بصرف النظر عن فائدة النقل نفسه ، فقد رأينا أيضًا كيفية تنفيذ آليات بسيطة للتخزين المؤقت والجلب المسبق لتسريع تحميل الصفحات الجديدة.

تستند هذه المقالة بأكملها إلى تجربتي الشخصية وما تعلمته من تنفيذ انتقالات الصفحة في المشاريع التي عملت عليها. إذا كانت لديك أي أسئلة ، فلا تتردد في ترك تعليق أو التواصل معي على Twitter - معلوماتي أدناه!

مزيد من القراءة على SmashingMag:

  • انتقالات ذكية في تصميم تجربة المستخدم
  • التصميم في الانتقال إلى عالم متعدد الأجهزة
  • توفير تجربة أصلية مع تقنيات الويب