تأثير اختلاف المنظر عند التمرير هو أسلوب مرئي تتحرك فيه طبقات مختلفة من المحتوى بسرعات متفاوتة أثناء انتقال المستخدم إلى أسفل الصفحة. يؤدي ذلك إلى خلق وهم بالعمق، حيث تظهر عناصر المقدّمة وكأنّها تتحرك بشكل أسرع من عناصر الخلفية، ما يوفّر تجربة تصفّح تفاعلية وغامرة. يمكن تحقيق هذا التأثير على أفضل وجه باستخدام "الصور المتحركة المستندة إلى الانتقال" في CSS، والتي تتيح لك ربط الصور المتحركة بموضع الانتقال في حاوية.
كيفية الإعداد
في ما يلي كيفية إنشاء تأثير اختلاف المنظر الأساسي:
إنشاء عنصر تضمين: يجمع هذا العنصر ببساطة كل طبقات تأثير المنظر معًا. وهو ليس العنصر القابل للتمرير، لذا يجب اقتصاص الفائض منه. امنحها أيضًا
heightيتطابق مع ارتفاع إحدى طبقات تأثير اختلاف المنظر.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }تحديد الطبقات: داخل الغلاف، أضِف الطبقات الفردية التي ستتحرك بسرعات مختلفة.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>إضافة حركة ترجمة: حدِّد حركة CSS التي تغيّر السمة
transformللطبقات. للحصول على تأثير اختلاف المنظر، ستستخدم عادةًtranslateYلتحريك الطبقات عموديًا.@keyframes parallax { from { transform: translateY(700px); } }إعداد
view-timeline: لربط الحركة بموضع الانتقال، أنشئview-timelineعلى عنصر الغلاف ثم طبِّقه على الطبقات..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }تأخير الحركات: لجعل الطبقات تتحرك بسرعات مختلفة، يمكنكم استخدام إحدى الطريقتَين الرئيسيَّتَين: تأخير الإطارات الرئيسية أو تأخير
animation-range.يمكن أن تستخدم كلتا الطريقتين قيمًا مضمّنة في الرمز البرمجي أو يمكن أن تستخدم تنفيذ
sibling-index()/sibling-count(). تُعدّ القيم المضمّنة في الرمز البرمجي أسهل ومفيدة أيضًا عندما يكون لديك عدد محدود من الطبقات. يكون تنفيذsibling-index()/sibling-count()مفيدًا عندما يكون لديك العديد من الطبقات.توزيع الإطارات الرئيسية:
باستخدام القيم المضمّنة في الرمز البرمجي، يمكنك تحديد خاصية مخصّصة لكل طبقة للتحكّم يدويًا في إزاحة تأثير اختلاف المنظر.
.layer:nth-child(1) { --offset: 100px; } .layer:nth-child(2) { --offset: 200px; } .layer:nth-child(3) { --offset: 300px; } @keyframes parallax { from { transform: translateY(var(--offset)); } }باستخدام
sibling-index()، يمكنك السماح للدالةsibling-index()بإرجاع فهرس عنصر فرعي بين العناصر الشقيقة لحساب التأثير المتدرج تلقائيًا.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }توزيع
animation-range:باستخدام القيم المحدّدة في الرمز البرمجي، يمكنك تحديد حدود
animation-rangeبشكل صريح على كل طبقة على حدة..layer:nth-child(1) { animation-range: entry 25% exit 50%; } .layer:nth-child(2) { animation-range: entry 25% exit 75%; } .layer:nth-child(3) { animation-range: entry 25% exit 100%; }باستخدام
sibling-index()وsibling-count()، يمكنك حساب النطاق رياضيًا استنادًا إلى إجمالي عدد الطبقات (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
مثال على الرمز
@keyframes parallax {
from {
transform: translateY(calc(100px * sibling-index()));
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
بدلاً من ذلك، يمكنك استخدام السمة animation-range لتحقيق تأثير مماثل:
@keyframes parallax {
from {
transform: translateY(700px);
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
أفضل الممارسات
عند استخدام الصور المتحركة المستندة إلى الانتقال، من المهم اتّباع بعض أفضل الممارسات لضمان تجربة سلسة وسهلة الاستخدام:
- يجب تضمين ميزة رصد الميزات: لا تتيح بعض المتصفّحات استخدام الصور المتحركة المستندة إلى الانتقال للأعلى أو للأسفل. استخدِم
@supports ((animation-timeline: view()) and (animation-range: entry))للتحقّق من توفّر الدعم وتوفير خيار احتياطي للمتصفّحات التي لا تتيح هذه الميزة.- يجب تضمين عملية التحقّق من
(animation-range: entry)هنا لفلترة المتصفّحات التي توفّر دعمًا جزئيًا فقط. - لا تستخدِم حزمة
scroll-timeline-polyfillلاستراتيجية الاحتياط لأنّها لا تتضمّن جميع الميزات وتحتوي على الكثير من المشاكل المعروفة. - إذا كانت الحركة تُعتبر زخرفية فقط، فاختَر "التحسين التدريجي" ولا تقدّم بديلاً.
- يجب تضمين عملية التحقّق من
- يجب احترام الإعدادات المفضّلة للمستخدمين: يفضّل بعض المستخدمين تقليل الحركة على الويب. استخدِم الاستعلام عن الوسائط
prefers-reduced-motionلإيقاف الصور المتحركة أو تقليلها لهؤلاء المستخدمين. - يُرجى محاولة تحريك خصائص CSS ذات الأداء العالي فقط: للحصول على صور متحركة أكثر سلاسة، التزِم بتحريك الخصائص التي يمكن معالجتها من خلال سلسلة عمليات التجميع في المتصفّح، مثل
transformوopacity. قد تؤدي إضافة تأثيرات حركية إلى سمات أخرى، مثلwidthأوheight، إلى حدوث مشاكل في الأداء. - يجب استخدام ترتيب التعريف الصحيح: عند استخدام السمة المختصرة
animation، يجب تعريفanimation-timelineوanimation-rangeبعدها لمنع السمة المختصرة من إعادة ضبط المخطط الزمني.
بالنسبة إلى ضبط animation-range:
- يجب منح جميع الطبقات الإزاحة نفسها عند البدء، مثل
entry 25% - يجب منح جميع الطبقات إزاحة نهاية مختلفة تستخدم
sibling-count()وsibling-index()لتوزيع الإزاحات، مثلexit calc(100% / sibling-count() * sibling-index()).
توافق المتصفّح واستراتيجيات الاحتياط
لذلك، عادةً ما تكون هناك حاجة إلى استراتيجية احتياطية.
بالنسبة إلى المتصفّحات التي لا تتيح استخدام الصور المتحركة المستندة إلى الانتقال للأعلى أو للأسفل، يمكنك استخدام خيار احتياطي لإعادة إنشاء التأثيرات المرئية. يتم عادةً إنشاء الإجراءات الاحتياطية باستخدام أداة استماع للتمرير (لتأثيرات ScrollTimeline) أو واجهة برمجة التطبيقات IntersectionObserver (لتأثيرات ViewTimeline).
في المتصفّحات التي تتضمّن دعمًا مدمجًا للصور المتحركة المستندة إلى الانتقال، استخدِم دائمًا تنفيذ CSS الأصلي لأنّها أكثر فعالية.
يُرجى العِلم أنّه لا يمكن إعادة إنشاء كل تأثير باستخدام نهج الإجراءات الاحتياطية.
بالنسبة إلى حالة الاستخدام هذه تحديدًا، يطبّق النص البرمجي التالي الإجراء الاحتياطي للمتصفّحات التي لا تتيح استخدام الصور المتحركة المستندة إلى الانتقال للأعلى أو للأسفل. يستخدم هذا الرمز IntersectionObserver لتتبُّع مستوى ظهور العنصر .wrapper ويعدّل السمة transform للطبقات استنادًا إلى موضع الانتقال في الصفحة.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const wrapper = document.querySelector('.wrapper');
const layers = document.querySelectorAll('.layer');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window.addEventListener('scroll', onScroll);
} else {
window.removeEventListener('scroll', onScroll);
}
});
}, { threshold: 0 });
observer.observe(wrapper);
function onScroll() {
const scrollY = window.scrollY;
const wrapperRect = wrapper.getBoundingClientRect();
const wrapperTop = wrapperRect.top + scrollY;
const wrapperHeight = wrapperRect.height;
const windowHeight = window.innerHeight;
if (scrollY >= wrapperTop - windowHeight && scrollY <= wrapperTop + wrapperHeight) {
const scrollPercent = (scrollY - (wrapperTop - windowHeight)) / (wrapperHeight + windowHeight);
layers.forEach((layer, index) => {
// This matches the effect as defined in the CSS example above.
// Customize this further if needed.
const initialTranslateY = 100 * index;
const translateY = initialTranslateY * (1 - scrollPercent);
layer.style.transform = `translateY(${translateY}px)`;
});
}
}
// Trigger onScroll once to set initial positions
onScroll();
}