اذهب إلى المحتوى

تأثيرات التمرير في صفحات الويب باستخدام Javascript وCSS– الجزء الثاني


عبد اللطيف ايمش

أهلًا بك في هذه السلسلة التي تتحدث عن تأثيرات التمرير (Scrolling Effects)، سنستعرض في هذه السلسلة عددٌ من تأثيرات التمرير وسنشرح آلية عملها وسنجرِّبها عمليًا.
يمكننا الاستفادة من الحدث scroll في JavaScript لإجراء تأثيرات عند تمرير صفحة الويب؛ لكن إن فعلنا ذلك دون إتقان فالنتيجة كارثية، أما إذا أحسنا صنعنا فيمكن لتأثيرات التمرير أن تبهر الزوار وتشعرهم أنَّ موقعك مميز.
تحدثنا في المقالة السابقة عن التأثيرات الآتية:

  • إخفاء صورة خلفية تدريجيًا عند التمرير
  • توضيح الصورة عند التمرير
  • تدوير العناصر عند التمرير
  • تأثير اختلاف المظهر parallax

أما المقالة الحالية (الثانية) فهي تتضمن التأثيرات الآتية:

  • إظهار صورة الخلفية عند التمرير باستخدام CSS فقط
  • تمرير سلس للصفحة
  • تطبيق تأثير عدم الوضوح على المحتوى خلف شريط الانتقال

وسنشرح في آخر مقالة طريقة إنشاء:

  • عنصر قابل للتمرير مع إمكانية وصول مخصصة من لوحة المفاتيح
  • تأثير غروب الشمس باستخدام SVG
  • إظهار فيديو في الخلفية
  • صور متحركة بتأثير parallax باستخدام CSS 3D و JavaScript

سأقدِّم لك في بداية كل قسم رابطًا لتجربة المثال تجربةً حيةً على المتصفح. سيسهل عليك كثيرًا أن تفهم الشرح والشيفرات بعد تجربتك للتأثير.

إظهار صورة الخلفية عند التمرير باستخدام CSS فقط

1-EasyFullscreenPageScrollWithBackgroundReveal.jpg

(تجربة حية)
طريقة عرض جميلة وشائعة هي «إظهار صورة الخلفية عند التمرير»: فكلما مَرَّرنا إلى الأسفل، فستصبح صورة الخلفية المخفية ظاهرةً للمستخدم.
صحيحٌ أنَّ طريقة العرض هذه أصبحت شائعة في الآونة الأخيرة، إلا أنَّها جيدة حيث تجعل المحتوى مركَّزًا ومختصرًا إلى بضع صور وفقرات قصيرة من النص.
يُحقَّق هذا التأثير عادةً باستخدام إطار عمل من إطارات JavaScript مع إحدى الإضافات؛ وهذا غير ضروري أبدًا في المتصفحات الحديثة، وسأريك طريقة فعل ذلك.

«النوافذ والجدران»

الفكرة الأساسية لهذا التأثير هي إنشاء سلسلة من «النوافذ» المفتوحة و«الجدران» المغلقة فوق بعضها بعضًا، وكلٌ منها له نفس الطول والعرض الخاص بإطار العرض (viewport).
لنبدأ بإنشاء شيفرة HTML بسيطة. يمكن إنشاء «النوافذ» و«الجدران» من أيّ عنصر HTML قادر على احتواء العناصر الأخرى؛ وسنستخدم في هذا المثال وسوم <section>:

<section>
    <h1>Come To Iceland</h1>
</section>
<section>
    <h1>The last settled part of Europe, much of Iceland remains pristine and untouched.</h1>
</section>

لجعل قياس جميع العناصر صحيحًا، فسأحذف أيّة هوامش (margin) من العنصر <body>، تأكد أنَّ العناصر <section> مُقاسة عبر border-box، وتأكد أنَّ لكل عنصرٍ منها له نفس ارتفاع إطار العرض باستخدام واحدات vm. يجب تنسيق النص ليستخدم نفس الواحدات السابقة:

body { margin: 0; }
section {
    box-sizing: border-box;
    height: 100vh; 
    text-align: center; 
    padding: 2vw;
    font-size: 6vw;
}

عناصر <section> تأخذ كامل عرض نافذة المتصفح، ولتوسيط محتواها فسنستعمل flexbox، ويجب إجراء المثل على عناصر <section> التي ستعرض صور الخلفية، لذا سأُضمِّن أنماطها أيضًا:

section {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center; 
    flex-direction: column;
    background-size: cover;
    background-repeat: no-repeat;
    background-attachment: fixed;
}

تُستخدم الخاصية flex-direction لإغلاق أيّة «نوافذ» التي تحتوي عدِّة أسطر نصية. ويجب علينا إضافة نمط خاص بأوّل عنصر <section> لأنَّ النص فيه أكبر، ولونه أبيض، ومكتوبٌ بأحرفٍ كبيرة، مع استخدام الخاصية text-shadow لإضافة ظل لتميزه عن الخلفية:

section:first-of-type {
    text-transform: uppercase;
    color: #fff;
    font-size: 8vw;
    text-shadow: 0 0 5px rgba(0,0,0,0.4);
}

عناصر <section> الزوجية لها خلفية بيضاء:

section:nth-of-type(even) {
    background: #fff;
}

أما عناصر <section> الفردية فلها صور خلفية:

section:nth-of-type(1) {
    background-image: url(iceland-fjords.jpg);
}
section:nth-of-type(3) {
    background-image: url(iceland-pool-faces.jpg);
}
section:nth-of-type(5) {
    background-image: url(iceland-ice.jpg);
}

وهذا كل ما في الأمر! صحيحٌ أنَّ المثال السابق يعمل على المتصفحات الحديثة فقط والتي تدعم vh و vm و flexbox؛ وإذا كنتَ تنوي دعم المتصفحات القديمة فعليك استخدام بدائل مثل نمط العرض table-row لكل عنصر <section>

تمرير سلس للصفحة

2-SmoothPageScrollinPureJavaScript.jpg

(تجربة حية)
تتواجد في HTML ميزة الانتقال إلى أماكن معيّن في الصفحة، وذلك بتوفير خاصية id للعنصر الذي تريد الانتقال إليه، ومن ثم ربط ذلك عبر عناصر <a>.
لكن الحركة غير سلسة وستنتقل فوريًا إلى العنصر الهدف؛ ولكن بغرض تحسين طريقة عرض واستخدام الموقع، فيتطلّب أحيانًا تصميم الموقع أن يتم التمرير بشكلٍ سلسٍ أو بطيء إلى نقطةٍ معيّنةٍ في الصفحة.
قديمًا كانت تُستعمَل jQuery لذلك، لكن من غير المعقول تحميل إطار عمل كامل لاستخدام هذه الميزة فقط؛ وتوفِّر JavaScript طريقةً أفضل وأسرع (من ناحية الأداء) وهي الدالة window.scrollTo.
سنستخدم عنصر <a> كأساس لهذه التقنية، وبهذه الطريقة سيتم الانتقال إلى الهدف حتى لو لم تعمل شيفرة التمرير السلس لسببٍ من الأسباب.

<a href="#destination">Click me: I’m <em>smoooooth</em>.</a><p id="destination">This is the target, further down the page.

وكما هو واضح، يجب أن تكون الصفحة طويلةً بعض الشيء وتمتد إلى ما بعد أسفل إطار العرض لكي تعمل هذه التقنية، لأنَّه إذا عُرِضَت كامل محتويات الصفحة في نافذة المتصفح، فلا حاجة إلى التمرير من الأساس؛ ولهذا السبب عليك أن تضع الكثير من المحتوى في الصفحة للتجربة.

طريقتان للتمرير بسلاسة

قد يبدو الأمر مربكًا بعض الشيء، إلا أنَّ البنية البرمجية للتمرير السلس موجودة في CSS وفي JavaScript، وأنَّ بعض المتصفحات تدعم تلك البنية البرمجية والأخرى لا تدعمها (انظر فقرة «دعم المتصفحات» في الأسفل).
ففي CSS إذا أردنا أنَّ يكون التمرير سلسًا لأحد العناصر (وعادةً ما نستخدم العنصر body لكن ذلك ليس ضروريًا) فعلينا أن نضبط الخاصية scroll-behavior ونسند القيمة smooth إليها:

body {
    scroll-behavior: smooth;
}

لاحظ أنَّ كلمة «behavior» (في scroll-behavior) لا تحتوي على حرف «u».

طريقة JavaScript

سنضيف شيفرة JavaScript إلى نهاية الصفحة لكي تُنفَّذ بعد انتهاء تحميل الصفحة:

var anchorLink = document.querySelector("a[href='#destination']"),
target = document.getElementById("destination");
anchorLink.addEventListener("click", function(e) {
    if (window.scrollTo) {
        e.preventDefault();
        window.scrollTo({"behavior": "smooth", "top": target.offsetTop});
    }
})

الدالة querySelector تستعمل نفس طريقة كتابة محدِّدات CSS للعثور على أوّل رابط الذي يشير إلى ‎#destination؛ والضغط على هذا الرابط سيؤدي إلى تنفيذ عبارة شرطية للتأكد من دعم المتصفح للدالة scrollTo، فإن دعمها المتصفح فستوقف الدالة e.preventDefault المتصفحَ من الانتقال مباشرةً إلى الفقرة الهدف، وسنستخدم الدالة scrollTo بدلًا من ذلك بعد ضبطها ليكون التمرير سلسًا. تأخذ الدالة scrollTo وسيطين هما behavior و top، مع وسيطٍ اختياريٍ هوleft، ويقبل آخر وسطين إحداثيات المكان الذي نريد الانتقال إليه.
يمكن استخدام الدالة
window.scrollفي المثال السابق، لأنَّ وظيفتها مماثلة لوظيفة الدالةwindow.scrollTo`.
مقارنةً مع استخدام إطار عمل، فإنَّ هذه الطريقة أبسط بكثير؛ لكن الجانب السلبي لها هو أنَّها لا تسمح للمصمم بتغيير دالة التوقيت أو حركات التمرير، لتجنّب استعمال المصممين استعمالًا سلبيًا لها.

إنشاء سكربت عام

السكربت السابق يعمل عمله بشكلٍ صحيح، لكنه يتطلب أن تعرف ما هو اسم العنصر الهدف، ولا يمكن تطبيقه إلا على رابطٍ وحيد. ماذا إذا أردت إنشاء سلسلة من الروابط التي تُشير إلى عناصر مختلفة؟ سنحتاج في هذه الحالة إلى جعل السكربت عامًا.
سنبدأ السكربت بإنشاء دالة التي تسمح لنا بمعالجة كل رابط بحلقة forEach:

var forEach = function (array, callback, scope) {
  for (var i = 0; i < array.length; i++) {
    callback.call(scope, i, array[i]);
  }
};

ثم سأُحدِّد جميع الروابط، وأرى ما هو العنصر التي تشير إليه، ومن ثم سأطبِّق scrollTo عند النقر عليها:

var anchorLinks = document.querySelectorAll("a[href^='#']");
if (window.scrollTo) {
    forEach(anchorLinks, function(index, element) {
        var target = document.getElementById(element.getAttribute("href").substring(1));
        element.addEventListener("click", function(el) {
            el.preventDefault();
            window.scrollTo(0, target.offsetTop); 
        })
    });
}

لاحظ أنَّ هذا قد يتطلب وضع الخاصية scroll-behavior لعنصر body كما سبق ذكره.

دعم المتصفحات

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

تطبيق تأثير عدم الوضوح على المحتوى خلف شريط الانتقال

3-EasyScrollBehindBluredNavigationBar.jpg

(تجربة حية)
أحد أنماط تصميم الواجهات الشائعة خصوصًا بعد إصدار نسخة iOS 7 هو شريط الانتقال الذي تظهر محتويات الصفحة التي خلفه مشوشةً؛ ربما تظن أنَّ تطبيق المُرشِّح blur في CSS هو الطريقة السهلة والواضحة لتطبيق هذا التأثير في صفحة ويب، وهذا صحيحٌ إلا أنَّ هنالك إشكالية: تأثيرات CSS ستُطبَّق على المحتوى داخل العنصر وليس خلفه. أي لا يوجد تأثير من تأثيرات CSS الذي يمكن أن يؤدي إلى تشويش العناصر خلفه، باستثناء خاصية اختبارية متوافرة في متصفح Safari 9 فقط باسم ‎-webkit-backdrop-filter-؛ لكن بالطبع سنجد حلًا!

إنشاء شريط الانتقال

الشريط نفسه هو عنصرٌ فارغٌ يملك القيمة fixed للخاصية position، وعناصر التنقل تأتي «فوق» ذاك العنصر باستعمال نفس الأبعاد. سأكتبُ ذلك باستخدامSass لأنَّها أقصر:

#blurrycontent {
    padding: 1rem;
    top: 0; left: 0;
    width: 100%;
    height: 5rem;
    overflow: hidden;
    position: fixed;
    filter: blur(4px);
}
nav {
    @extend #blurrycontent;
    filter: none;
    text-align: right;
}

يجب أن تتواجد بقية الصفحة داخل العنصر <main>، وليس داخل العنصر <body>، وذلك لأسبابٍ سنوضِّحها بعد لحظات:

<main id="content">
    <h1>London</h1>
    <p>With roots at least 7,000 years old, London is an accretion of artifacts old and new, from the remnants of wooden Neolithic settlements buried in the mud of the Thames to gleaming 21st century spires of glass and steel…
</main>

ولأنَّ العنصرين ‎#blurrycontent و <nav> متموضعَين فوق بعضهما في مكانٍ ثابتٍ في أعلى الشاشة، فيجب تنسيق العنصر <main> لكي يأخذ مكان بقية المحتوى:

main {
    margin: 0;
    background: url(london_background.jpg);
    background-size: cover;
    padding: 2rem;
}

حسنًا، تبدو الصفحة جيدةً، لكننا لم نرَ أيّة تأثيرات في منطقة شريط التنقل عندما يتم التمرير؛ وهذا ما سنفعله في الخطوة الآتية.

إنشاء تأثير «الزجاج»!

كما ذكرتُ في البداية، لا تُطبّق تأثيرات CSS إلا على المحتوى الموجود داخل العنصر، وليس تحته؛ لذا سنأخذ نسخةً من العنصر <main> ونضعها داخل العنصر ‎#blurrycontent باستخدام الدالة cloneNode عبر سكربت موجود في أسفل الصفحة:

var pageContent = document.getElementById("content"),
pagecopy = pageContent.cloneNode(true),
blurryContent = document.getElementById("blurrycontent");
blurryContent.appendChild(pagecopy);

قد لا تستطيع ملاحظة التأثير في هذه المرحلة، لأنَّ المحتوى الموجود داخل العنصر ‎#blurrycontent لن يُمرَّر مع بقية المستند، وعلينا مزامنة حركتهما بإضافة السطر الآتي إلى السكربت:

window.onscroll = function() { 
        blurryContent.scrollTop = window.pageYOffset;
}

بعد أن ربطناهما مع بعضهما بعضًا، فيمكننا أن نُمرِّر الصفحة وسنحصل على نفس المحتوى الموجود تحت شريط التنقل في عنصر ‎#blurrycontent لكنه مشوش. ولأنَّ العنصر <nav> غير موجود داخل العنصر ‎#blurrycontent فلن يخضع لتأثير عدم الوضوح.

محدوديات هذه الطريقة

كما هو واضح، إنشاء نسخة من محتوى الصفحة وتطبيق تأثير عدم الوضوح عليها سيؤدي إلى عبءٍ إضافيٍ على المتصفح وعلى المعالج الرسومي، لذا كن حذرًا في ذلك وقدِّر كمية المحتوى الموجود ضمن العنصر <main> قبل نسخه. الدالة cloneNode تنسخ العنصر نسخًا حيًا، أي أنَّ أيّة تعديلات على العنصر الأصلي ستُطبَّق أيضًا على العنصر المنسوخ، لكن ربما تلاحظ تأثيرًا بسيطًا حتى تتم مزامنة كلا النسختين.
هذه أربع نقاط أخيرة يجب ملاحظتها:

  • لأنَّ بعض إصدارات متصفح Internet Explorer لا تدعم تأثيرات CSS (وأوقف المتصفح دعم النسخة الخاصة به من هذا التأثير، والتي كانت متاحةً في الإصدارات القديمة منه)، فلن تلاحظ أيّة تغييرات في شريط التنقل في متصفح IE.
  • يجب أن تتجنب استخدام العناصر ذات الموضع الثابت (fixed) في صفحات الويب على الهواتف الذكية والأجهزة اللوحية، فمنذ فترةٍ قريبةٍ كانت طريقة تعامل متصفحات الهواتف مع position: fixed سيئةً، وسيتم حجز مساحة من الشاشة الصغيرة. وصحيحٌ أنَّ هذا التأثير مستوحى من أحد أنظمة الهواتف، ألا أنَّه من الأفضل إيقافه في الشاشات الصغيرة باستخدام مجموعة من media queries (أو يمكنك أن تصمم الموقع للهواتف أولًا [mobile-first] ولا تُشغِّل التأثير حتى تصبح الشاشة بمقاسٍ معيّن).
  • يجب أخذ قابلية الوصول (Accessibility) بعين الاعتبار عند إنشاء مثل هذا التأثير، فستُفسِّر قارئات الشاشة شجرة DOM، وليس ما تراه على الشاشة، وهذا يعني أنَّ قارئات الشاشة ستحصل على نسختين من محتوى الصفحة افتراضيًا، ولتنجب ذلك فسأضع aria-hidden="true"‎ في العنصر ‎#blurrycontent:
<div id="blurrycontent" aria-hidden="true"></div>

وبهذا سترى قارئات الشاشة النسخةَ الأصلية من الصفحة فقط دونًا عن النسخة الموجودة في العنصر ‎#blurrycontent.

  • يجب أن تكون حذرًا عند نسخ العناصر التي لها الخاصية id، فقد يؤدي تكرار قيم الخاصية id إلى تضاربات ومشاكل في CSS و JavaScript.

ترجمة وبتصرّف للمقالات Background Reveal Scroll In Pure CSSو Smooth Page Scroll in 5 Lines of JavaScript و Scroll-Behind Blurred Site Navigation Barلصاحبها Dudley Storey


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...