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

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


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

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

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

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

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

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

إخفاء صورة خلفية تدريجيًا عند التمرير

1-ResponsiveBackgroundImageFadeonScroll.jpg

(تجربة حية)


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

طريقة التفافية للتعويض عن عدم وجود الخاصية background-opacity

للأسف لا يوجد لحد الآن خاصية باسم background-opacity لتحديد شفافية الخلفية؛ لكن من الممكن إنشاء التأثير عبر استخدام ميزة تعدد الخلفيات في CSS: حيث سنضع الصورة كطبقة (layer)، ثم الطبقة الثانية هي تدرجٌ لوني سنستخدم في ألوانه الشفافية alpha. لذا ستبدو شيفرة CSS كالآتي:

body {
    background: linear-gradient(rgba(255, 255, 255, 0), 
        rgba(255, 255, 255, 0)), url(times-square-perspective.jpg);
    background-repeat: no-repeat;
    background-attachment: fixed !important;
    background-size: 100% !important;
    background-position: center top !important;
    padding: 1rem;
    padding-top: 45%;
    color: #fff;
}

التدرج اللوني (الذي سيتم وضعه فوق الصورة، لأنَّه عُرِّفَ أولًا) غير ظاهر حاليًا، لأنَّ قيمة الشفافية alpha هي 0 للونين المشكلين للتدرج. استخدمنا الكلمة المفتاحية ‎!important للتأكّد أنَّ شيفرة JavaScript –التي سنُطبِّقها بعد قليل– لن تتمكن من إلغاء قيم خاصيات CSS السابقة.
شيفرة HTML تحتوي على ما يلي:

<h1>New York Stories</h1>
    <p>In my younger and more vulnerable years…

في النهاية، علينا وضع العنصر h1 في الأعلى:

h1 {
    text-shadow: 0 0 5px rgba(0,0,0,0.5);
    font-size: 4rem; color: #fff;
    line-height: 1; position: absolute;
    top: 10px;
}

ولكي تصبح الفقرة قابلةً للقراءة، فعلينا أن نحوِّل التدرج اللوني إلى اللون الأبيض تمامًا، مما يؤدي إلى إخفاء صورة الخلفية.

تعديل التدرجات اللوني في CSS باستخدام JavaScript

أضف شيفرة JavaScript الآتية في أسفل الصفحة:

var nystories = document.querySelector("p").offsetTop;
window.onscroll = function() {
  if (window.pageYOffset > 0) {
    var opac = window.pageYOffset / nystories;
    document.body.style.background = "linear-gradient(rgba(255, 255, 255, " + opac + "), 
        rgba(255, 255, 255, " + opac + ")), 
        url(times-square-perspective.jpg) no-repeat";
    }
}

في الشيفرة السابقة، قيمةُ المتغير nystories هي موضع أوّل فقرة في الصفحة. عندما يبدأ المستخدم بالتمرير فستُنشِئ الشيفرة السابقة متغيرًا باسم opac الذي سيُقسِّم الموضع «الحالي» للنافذة على الموضع «البدائي» لأول فقرة. ومن ثم ستُجمَع النتيجة مع قيم rgb لألوان التدرج اللوني مكان قيمة الشفافية alpha، مما يعطي التأثير بإخفاء صورة الخلفية عندما يتم تمرير الصفحة.
هذا أحد الأمثلة البسيطة عن التمرير، ما زال في جعبتنا المزيد.

توضيح الصورة عند التمرير

2-Scroll-to-FocusForHeroImages.jpg

(تجربة حية)


أعجبتني اللمسة الجميلة في تطبيق توتير على هواتف iPhone: عندما تمرّر إلى الأسفل في صفحة «Me» فستصبح صورة الترويسة غير واضحة وسيتم تقريبها. أرى أنَّ من المفيد تقليد هذه التقنية في متصفحات الويب، خصوصًا للصور البارزة التابعة للمقالات…
شيفرة HTML بسيطة للغاية:

<article>
    <header>
        <img src="placid-pond.jpg" alt>
    </header></article>

وفي نفس الصفحة سنضيف شيفرة SVG:

<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
  <filter id="blur">
    <feGaussianBlur stdDeviation="0" />
  </filter>
</svg>

الغرض من وضع شيفرة SVG السابقة هو إضافة تأثير عدم الوضوح (blur) في متصفح Firefox، والذي لا يدعم إلى الآن «مُرشّحات CSS» ‏(CSS Filters)؛ لاحظ أنَّ خاصية العرض والارتفاع لعنصر svg لها القيمة 0، لذا لن يؤثِّر على تخطيط الصفحة.
سنضيف تأثير عدم الوضوح عبر أنماط CSS:

header {
    overflow: hidden;
    font-size: 0;
}
article header img {
    width: 100%;
    height: auto;
    filter: url(#blur);
}

شيفرة JavaScript أكثر تعقيدًا مما سبق. يجب في البداية أن نضع جميع الشيفرات داخل دالة التي ستستدعى عندما يتم التمرير في المتصفح:

window.onscroll = function (event) {  

}

سنحتاج إلى الكثير من المعلومات داخل هذه الدالة. المتغيرات التي سنستعملها هي:

var headerImg = document.querySelector("article header img"),
headerImgOffset = headerImg.getBoundingClientRect(),
imgTop = headerImgOffset.top,
imgBottom = headerImgOffset.bottom,
imgHeight = headerImg.offsetHeight,
viewportHeight = document.documentElement.clientHeight,
blur = document.getElementById("blur"),
svgblur = blur.getElementsByTagName("feGaussianBlur")[0],
topEffectStart = Math.abs((viewportHeight  imgHeight)/5);

 

اقرأ الأسطر السابقة بتمعّن، يجب أن تبدو لك المتغيرات منطقيةً:

  • المتغير headerImg يشير إلى صورة الترويسة الموجودة في أعلى المقالة (وللتبسيط، سأعتبر أنَّ الصفحة فيها مقالة وحيدة، وعنصر header وحيد).
  • المتغير headerImgOffset يسمح لنا بالوصول إلى مكان وأبعاد الصورة، وفي حالتنا سنستخدم imgTop و imgBottom و imgHeight.
  • المتغير viewportHeight يمثِّل ارتفاع نافذة المتصفح.
  • المتغير blur يشير إلى شيفرة SVG في الصفحة، أما svgBlur فهو العنصر الذي سيسبِّب تأثير عدم الوضوح في متصفح Firefox.
  • المتغير topEffectStart هو الفرق بين ارتفاع «إطار العرض» (viewport) الحالي وارتفاع الصورة، ومقسومًا على 5. وهذا يعني أنَّ تأثير عدم الوضوح سيبدأ فقط إذا كانت صورة الترويسة في الخُمس العلوي من نافذة المتصفح. استخدمتُ الدالة Math.abs لكي أحصل دومًا على عددٍ موجب، لأنَّ من الممكن أن تكون الصورة «أطول» من ارتفاع إطار العرض الحالي.

بعد توضيح كل ما سبق، فلنضف عبارةً شرطيةً داخل الدالة:

if (imgTop < topEffectStart && imgBottom > 0 ) { 
    blurFactor = Math.abs(imgTop / 100);
    scaleFactor = 1+Math.abs(imgTop / 1000);
    headerImg.style.webkitFilter = "blur(" + blurFactor + "px)";
    svgblur.setAttribute("stdDeviation", blurFactor);
    headerImg.style.webkitTransform = "scale(scaleFactor)";
    headerImg.style.transform = "scale(scaleFactor)";
}

هذا يعني إذا كان أعلى الصورة موجودٌ في أول خُمس من نافذة إطار العرض وما زالت الصورةُ معروضةً وظاهرةً للمستخدم، فافعل ما يلي:

  • أخذ الموقع الحالي لأعلى الصورة نسبةً إلى أعلى إطار العرض، وتقسيم الناتج على 100، وتحويل الناتج إلى عدد عشري موجب، وتسمية الناتج باسم blurFactor.
  • وبطريقةٍ مشابهة، سنأخذ الفرق، ونُقسِّمه على 1000 (لأن تغيير المقياس [scale] حساسٌ أكثر من تأثير عدم الوضوح) ومن ثم سنجمع العدد 1 مع الناتج، ونسمِّه scaleFactor.
  • سنُطبِّق blurFactor (بعد إضافة “px” إليه) على صورة الترويسة، وذلك بوضعه كقيمة لمُرشِّح Webkit Blur.
  • وبطريقةٍ مشابهة، سنُغيّر قيمة stdDeviation لنفس قيمة blurFactor وذلك لتطبيق التأثير في متصفح Firefox.
  • في النهاية، سنسند قيمة scaleFactor إلى خاصية transform في CSS لصورة الترويسة.

إحدى ميزات استخدام scale و blur معًا هي القدرة على إخفاء تأثير عدم الوضوح في جوانب الصورة، والتي لا يمكن إخفاؤها حتى باستخدام overflow: hidden في الحالات العادية.
يمكن استخدام نفس السكربت السابق مع بعض التعديلات لكي يتم التركيز أو توضيح الصورة عندما تكون في منتصف الصفحة، مما يجذب انتباه المستخدم إليها.

تدوير العناصر (المسننات) عند التمرير

3-RotatePageElementsonScrollwithJavaScript.jpg

(تجربة حية)


سأريك في هذا المثال كيفية تدوير العناصر أثناء تمرير الصفحة باستخدام JavaScript مباشرةً دون أيّة مكتبات خارجية.

ضبط المسننات

سيتم وضع المسننين في الصفحة باستخدام قيم id فريدة لكلٍ منهما؛ ولمّا كانا عبارةً عن رسوميات متجهة (vector shapes) فمن المعقول أن نُنشئهما باستخدام SVG:

<div id="gearbox">
    <img src="gear.svg" alt id="leftgear">
    <img src="gear.svg" alt id="rightgear">
</div>

هنالك الكثير من الخيارات لطريقة وضع المسننات: ففي أغلبية الأحيان سنستعمل موضعًا ثابتًا لها (أي position: fixed)، ومن الممكن أيضًا وضعها في مكانٍ معيّن ثم سيصبح موقعها ثابتًا عندما يتم التمرير بعدها (أي position: sticky). سأستعمل في هذا المثال flexbox لفصل العناصر، وسأستخدم الواحدات vm لقياسها؛ ويمكن أيضًا أن يكون موضع المسننات static وبالتالي ستختفي المسننات عند تمرير الصفحة.

#gearbox {
    display: flex;
    justify-content: space-between;
}
#leftgear, #rightgear {
    width: 20vw;
    max-width: 20%;
    height: auto;
}

تدوير المسننات

تدوير العناصر سهلٌ للغاية، كل ما عليك فعله هو إضافة الشيفرة الآتية في أسفل الصفحة:

var leftgear = document.getElementById("leftgear"),
rightgear = document.getElementById("rightgear");

window.addEventListener("scroll", function() {
    leftgear.style.transform = "rotate("+window.pageYOffset+"deg)";
    rightgear.style.transform = "rotate(-"+window.pageYOffset+"deg)";
});

سيدور كل مسنن بنفس مقدار تمرير النافذة مقدرًا بالبكسل والذي سيحوّل إلى درجات. لتسريع أو تبطيء دوران المسننات نسبةً إلى مقدار التمرير، فيمكنك جعل window.pageYOffset جزءًا من تعبيرٍ رياضيٍ بسيط (كالقسمة أو الضرب بالرقم 2 على سبيل المثال).

تفادي «تعليق» المسننات

مشكلة شائعة مع أيّة تقنية التي تتعامل مع التمرير هي «تعليق» العناصر حيث ستحاول مطابقة مدخلات المستخدم. أسهل حل للتعامل مع هذه الإشكالية هو تحريك العناصر عندما يصبح المتصفح جاهزًا للتعامل مع تلك الحركة، وذلك عبر requestAnimationFrame. ولفعل ذلك سنُجعل الدالة addEventListener تستمع إلى حدث خاص (custom event):

window.addEventListener("optimizedScroll", function() {
    
})

وقبل دالة معالجة الحدث، أضف دالةً مجهولةً (anonymous function):

;(function() {
    var throttle = function(type, name, obj) {
        var obj = obj || window;
        var running = false;
        var func = function() {
            if (running) { return; }
            running = true;
            requestAnimationFrame(function() {
                obj.dispatchEvent(new CustomEvent(name));
                running = false;
            });
        };
        obj.addEventListener(type, func);
    };
    throttle ("scroll", "optimizedScroll");
})();

طريقة سهلة لإنشاء تأثير اختلاف المظهر

4-Simple-EM-Parallax.jpg

(تجربة حية)


نَشَرَ «Adam Mustill» تقنيةً ذكيةً جدًا لإنشاء تأثير اختلاف المظهر (Parallax) عبر تعديل واحدة root em. يتساءل الكثيرون عن كيفية إنشاء تأثيرات اختلاف المظهر، وارتأيتُ أنَّ هذه الطريقة قد تكون نقطة بداية جيدة لتعلم إنشاء هذا التأثير.
تأثير اختلاف المظهر (Parallax) يتطلب عادةً عمليات معالجة معقدة لعدد كبير من عناصر DOM المنفصلة، لكن التقنية التي سنستخدمها هنا تُسهِّل علينا معالجة العناصر بتغيير قياسٍ وحيد، وهي تستغل ميزة غريبة بعض الشيء لواحدة القياس em: إذا كان قياس العنصر الأب والعناصر الأبناء بواحدة em، فإن القياس النهائي للأبناء هو ناتج ضربها مع بعضها. وصحيحٌ أنَّ هذا السلوك قد يُسبِّب لنا صداعًا (وهو سببٌ وجيهٌ لاستخدام rem إن استطعنا) إلا أنَّ له ميزتين في هذه الحالة:
1. وحدة القياس em لها دعم أقوى في المتصفحات نسبةً إلى rem.
2. تتطلب em وجود عنصر أب، وهذا يعني أنَّ تأثير اختلاف المظهر يمكن أن يُطبَّق على حاوية معيّنة؛ أما في طريقة rem التي ابتكرها Adam، فإنَّ كل شيءٍ موجود في عنصر <body> سيتأثّر بالتمرير إلا إذا اتخذتَ إجراءاتٍ معينةً للحد من ذلك.
ولقد حسّنتُ أيضًا من شيفرة Adam وجعلتها سطرًا وحيدًا من شيفرات JavaScript، بدلًا من عدِّة أسطر من jQuery، مما يُحسِّن من الأداء.

شيفرات HTML و CSS

شيفرة HTML لهذا المثال بسيطةٌ جدًا: إذ هنالك ثلاث صور وترويسة داخل عنصر <div>:

<div id="parallax">
    <h1>Simple EM Parallax Technique</h1>
    <img src="candles.jpg" alt>
    <img src="cherry-tree.jpg" alt>
    <img src="pagoda-surrounded-by-trees.jpg" alt>
</div>

وأنماط CSS ليست معقدةً أبدًا:

#parallax {
    background-image: url(blurred-background-small.jpg);
    background-size: cover;
    padding-top: 62.5%; 
    overflow: hidden;
    position: relative;
    font-size: .1em;
}
#parallax * { position: absolute; }
#parallax img {
    width: 40%; height: auto;
    box-shadow: 0 .2em 8px 4px rgba(0,0,0,0.5);
}
#parallax h1 { 
    font-size:3rem; color: #fff; z-index: 2;
    top: 0; text-transform: uppercase;
    width: 100%; text-align: center;
    text-shadow: 0 .2em 5px rgba(0,0,0,0.4); 
}
#parallax img:nth-of-type(1) {
    left: 5%; bottom: 22em;
}
#parallax img:nth-of-type(2) {
    left: 28%; z-index: 3; bottom: 8em;
}
#parallax img:nth-of-type(3) {
    left: 55%; bottom: 12em;
}

الأمور المهمة التي يجب ملاحظتها هي القيمة الافتراضية الصغيرة جدًا لقياس الحاوية ‎#parallax بواحدة em، بالإضافة إلى قيم bottom لكل صورة، وأنَّ نص الترويسة مقاسٌ بواحدة rem وليس em.

شيفرة JavaScript

شيفرة JavaScript هي عبارة عن سطرٍ وحيدٍ فقط يجري تعديلًا على إحدى خاصيات CSS:

window.onscroll = function() {
    if (window.pageYOffset > 0) {
        document.getElementById("parallax").style.fontSize = (window.pageYOffset/20)*.1+"em";
        }
    }

بشرح مبسّط: سيؤدي التمرير إلى تغيير قيمة الخاصية font-size لحاوية ‎#parallax، وهذا التغيير يعتمد على عدد البكسلات التي تم تمريرها، مقسومةً على 20، مضروبةً بقيمة ‎0.1 em‎ والتي هي حجم الخط الأصلي.
التمرير إلى الأسفل سيؤدي إلى زيادة قيمة الخاصية bottom لكل صورة مما يدفعها نحو الأعلى؛ لكن الترويسة لن تتأثر لأنها مُقاسة بواحدة rem، وهي ذات خاصية z-index مناسبة مما يسمح للصور بالمرور أمامها وخلفها.

الخلاصة

استعرضنا التأثيرات السابقة بسرعة، بقي عليك أن تتمعّن بشيفراتها، وتحاول إيجاد استخدامات أخرى للتقنيات التي رأيتها.
ترجمة وبتصرّف للمقالات:
Fade A Responsive Background Image On Scroll

Scroll-to-Focus Effect For Hero Images

Rotate Elements on Scroll with JavaScript

Easy Parallax Effects With Em

لصاحبها 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.


×
×
  • أضف...