دليل شامل لمطوري الويب حول التحكم في تدفق الرسوم المتحركة المعتمدة على التمرير في CSS. تعلم استخدام animation-direction مع animation-timeline لإنشاء تجارب مستخدم ديناميكية ومدركة للاتجاه.
إتقان اتجاه التحريك في الرسوم المتحركة المعتمدة على التمرير في CSS: نظرة معمقة على التحكم في التدفق
لسنوات، كان إنشاء الرسوم المتحركة التي تستجيب لموضع تمرير المستخدم هو حكر على لغة JavaScript. أصبحت مكتبات مثل GSAP و ScrollMagic أدوات أساسية، لكنها غالبًا ما كانت تأتي بتكلفة على الأداء، حيث تعمل على الخيط الرئيسي (main thread) وتؤدي أحيانًا إلى تجارب متقطعة. لقد تطورت منصة الويب، واليوم، لدينا حل ثوري وعالي الأداء وتعريفي مدمج مباشرة في المتصفح: الرسوم المتحركة المعتمدة على التمرير في CSS.
تسمح لنا هذه الوحدة الجديدة القوية بربط تقدم الرسوم المتحركة مباشرة بموضع تمرير حاوية أو بمدى رؤية عنصر في منفذ العرض (viewport). في حين أن هذه قفزة هائلة إلى الأمام، إلا أنها تقدم نموذجًا فكريًا جديدًا. أحد أهم الجوانب التي يجب إتقانها هو التحكم في كيفية تصرف الرسوم المتحركة عندما يقوم المستخدم بالتمرير للأمام مقابل الخلف. كيف تجعل عنصرًا يتحرك للداخل عند التمرير لأسفل، ويتحرك للخارج عند التمرير لأعلى؟ تكمن الإجابة في خاصية CSS مألوفة تم منحها غرضًا جديدًا وقويًا: animation-direction.
سيأخذك هذا الدليل الشامل في رحلة معمقة للتحكم في تدفق واتجاه الرسوم المتحركة المعتمدة على التمرير. سنستكشف كيف تم إعادة توظيف animation-direction، ونحلل سلوكها بأمثلة عملية، ونزودك بالمعرفة اللازمة لبناء واجهات مستخدم متطورة ومدركة للاتجاه تبدو بديهية ومذهلة.
أساسيات الرسوم المتحركة المعتمدة على التمرير
قبل أن نتمكن من التحكم في اتجاه رسومنا المتحركة، يجب أولاً أن نفهم الآليات الأساسية التي تحركها. إذا كنت جديدًا على هذا الموضوع، فسيكون هذا القسم بمثابة تمهيد حاسم. وإذا كنت على دراية به بالفعل، فهو بمثابة مراجعة رائعة للخصائص الرئيسية المستخدمة.
ما هي الرسوم المتحركة المعتمدة على التمرير؟
في جوهرها، الرسوم المتحركة المعتمدة على التمرير هي رسوم متحركة لا يرتبط تقدمها بساعة (أي بالوقت) ولكن بتقدم جدول زمني للتمرير (scroll timeline). فبدلاً من أن تستمر الرسوم المتحركة لمدة ثانيتين مثلاً، فإنها تستمر طوال مدة إجراء التمرير.
تخيل شريط تقدم في أعلى منشور مدونة. تقليديًا، كنت ستستخدم JavaScript للاستماع إلى أحداث التمرير وتحديث عرض الشريط. مع الرسوم المتحركة المعتمدة على التمرير، يمكنك ببساطة أن تخبر المتصفح: "اربط عرض شريط التقدم هذا بموضع تمرير الصفحة بأكملها." يتولى المتصفح بعد ذلك جميع الحسابات والتحديثات المعقدة بطريقة محسّنة للغاية، غالبًا خارج الخيط الرئيسي، مما ينتج عنه رسوم متحركة سلسة تمامًا.
الفوائد الرئيسية هي:
- الأداء: عن طريق تفريغ العمل من الخيط الرئيسي، نتجنب التعارض مع مهام JavaScript الأخرى، مما يؤدي إلى رسوم متحركة أكثر سلاسة وخالية من التقطعات.
- البساطة: ما كان يتطلب عشرات الأسطر من JavaScript يمكن تحقيقه الآن ببضعة أسطر من CSS التعريفية.
- تجربة مستخدم محسّنة: الرسوم المتحركة التي يتم التحكم فيها مباشرة بواسطة إدخال المستخدم تبدو أكثر استجابة وجاذبية، مما يخلق اتصالًا أقوى بين المستخدم والواجهة.
العناصر الفاعلة: `animation-timeline` والجداول الزمنية
يتم تنسيق السحر بواسطة خاصية animation-timeline، التي تخبر الرسوم المتحركة باتباع تقدم التمرير بدلاً من الساعة. هناك نوعان أساسيان من الجداول الزمنية التي ستواجهها:
1. الجدول الزمني لتقدم التمرير (Scroll Progress Timeline): يرتبط هذا الجدول الزمني بموضع التمرير داخل حاوية تمرير. يتتبع التقدم من بداية نطاق التمرير (0%) إلى نهايته (100%).
يتم تعريف ذلك باستخدام دالة scroll():
animation-timeline: scroll(root); — يتتبع موضع تمرير منفذ عرض المستند (عنصر التمرير الافتراضي).
animation-timeline: scroll(nearest); — يتتبع موضع تمرير أقرب حاوية تمرير رئيسية.
مثال: شريط تقدم بسيط للقراءة.
.progress-bar {
transform-origin: 0 50%;
transform: scaleX(0);
animation: fill-progress auto linear;
animation-timeline: scroll(root);
}
@keyframes fill-progress {
to { transform: scaleX(1); }
}
هنا، يتم تشغيل الرسوم المتحركة fill-progress بواسطة تمرير الصفحة الإجمالي. أثناء التمرير من الأعلى إلى الأسفل، يتقدم التحريك من 0% إلى 100%، مما يؤدي إلى تغيير حجم الشريط من 0 إلى 1.
2. الجدول الزمني لتقدم العرض (View Progress Timeline): يرتبط هذا الجدول الزمني بمدى رؤية عنصر داخل حاوية تمرير (غالبًا ما يطلق عليها منفذ العرض). يتتبع رحلة العنصر أثناء دخوله وعبوره وخروجه من منفذ العرض.
يتم تعريف ذلك باستخدام دالة view():
animation-timeline: view();
مثال: عنصر يتلاشى للظهور عندما يصبح مرئيًا.
.reveal-on-scroll {
opacity: 0;
animation: fade-in auto linear;
animation-timeline: view();
}
@keyframes fade-in {
to { opacity: 1; }
}
في هذه الحالة، تبدأ الرسوم المتحركة fade-in عندما يبدأ العنصر في دخول منفذ العرض وتكتمل عندما يكون مرئيًا بالكامل. يرتبط تقدم الجدول الزمني مباشرة بمدى الرؤية هذا.
المفهوم الأساسي: التحكم في اتجاه التحريك
الآن بعد أن فهمنا الأساسيات، دعنا نتناول السؤال المركزي: كيف نجعل هذه الرسوم المتحركة تتفاعل مع اتجاه التمرير؟ يقوم المستخدم بالتمرير لأسفل، فيظهر عنصر بالتلاشي. يعود للتمرير لأعلى، فيجب أن يختفي العنصر بالتلاشي. هذا السلوك ثنائي الاتجاه ضروري لإنشاء واجهات بديهية. وهنا يأتي دور animation-direction ليعود بقوة إلى الساحة.
إعادة النظر في `animation-direction`
في رسوم CSS المتحركة التقليدية المعتمدة على الوقت، تتحكم خاصية animation-direction في كيفية تقدم الرسوم المتحركة عبر إطاراتها الرئيسية (keyframes) على مدى تكرارات متعددة. قد تكون على دراية بقيمها:
normal: يعمل للأمام من 0% إلى 100% في كل دورة. (الافتراضي)reverse: يعمل للخلف من 100% إلى 0% في كل دورة.alternate: يعمل للأمام في الدورة الأولى، وللخلف في الثانية، وهكذا.alternate-reverse: يعمل للخلف في الدورة الأولى، وللأمام في الثانية، وهكذا.
عندما تطبق جدولًا زمنيًا للتمرير، يتلاشى مفهوم "التكرارات" و "الدورات" إلى حد كبير لأن تقدم الرسوم المتحركة مرتبط مباشرة بجدول زمني واحد ومستمر (على سبيل المثال، التمرير من الأعلى إلى الأسفل). يقوم المتصفح بذكاء بإعادة توظيف animation-direction لتعريف العلاقة بين تقدم الجدول الزمني وتقدم الرسوم المتحركة.
النموذج الفكري الجديد: تقدم الجدول الزمني مقابل تقدم الرسوم المتحركة
لفهم هذا حقًا، يجب أن تتوقف عن التفكير في الوقت وتبدأ في التفكير بمصطلحات تقدم الجدول الزمني. يمتد الجدول الزمني للتمرير من 0% (أعلى منطقة التمرير) إلى 100% (أسفل منطقة التمرير).
- التمرير لأسفل/للأمام: يزيد من تقدم الجدول الزمني (على سبيل المثال، من 10% إلى 50%).
- التمرير لأعلى/للخلف: يقلل من تقدم الجدول الزمني (على سبيل المثال، من 50% إلى 10%).
تحدد خاصية animation-direction الآن كيفية تعيين إطارات @keyframes الخاصة بك لتقدم هذا الجدول الزمني.
animation-direction: normal; (الافتراضي)
ينشئ هذا تعيينًا مباشرًا بنسبة 1 إلى 1.
- عندما يكون تقدم الجدول الزمني 0%، تكون الرسوم المتحركة عند إطارها الرئيسي 0%.
- عندما يكون تقدم الجدول الزمني 100%، تكون الرسوم المتحركة عند إطارها الرئيسي 100%.
لذا، أثناء التمرير لأسفل، تعمل الرسوم المتحركة للأمام. وأثناء التمرير لأعلى، يقل تقدم الجدول الزمني، لذا تعمل الرسوم المتحركة فعليًا بشكل عكسي. هذا هو السحر! السلوك ثنائي الاتجاه مدمج. لا تحتاج إلى القيام بأي شيء إضافي.
animation-direction: reverse;
ينشئ هذا تعيينًا معكوسًا.
- عندما يكون تقدم الجدول الزمني 0%، تكون الرسوم المتحركة عند إطارها الرئيسي 100%.
- عندما يكون تقدم الجدول الزمني 100%، تكون الرسوم المتحركة عند إطارها الرئيسي 0%.
هذا يعني أنه أثناء التمرير لأسفل، تعمل الرسوم المتحركة للخلف (من حالتها النهائية إلى حالتها الأولية). وأثناء التمرير لأعلى، يقل تقدم الجدول الزمني، مما يجعل الرسوم المتحركة تعمل للأمام (من حالتها الأولية نحو حالتها النهائية).
هذا التبديل البسيط قوي بشكل لا يصدق. دعنا نراه قيد التنفيذ.
التنفيذ العملي والأمثلة
النظرية رائعة، لكن دعنا نبني بعض الأمثلة الواقعية لترسيخ هذه المفاهيم. بالنسبة لمعظم هذه الأمثلة، سنستخدم جدولًا زمنيًا من نوع view()، لأنه شائع لعناصر واجهة المستخدم التي تتحرك عند ظهورها على الشاشة.
السيناريو 1: التأثير الكلاسيكي "الكشف عند التمرير"
الهدف: عنصر يظهر بالتلاشي وينزلق لأعلى أثناء التمرير لأسفل إلى نطاق رؤيته. وعند التمرير لأعلى مرة أخرى، يجب أن يختفي بالتلاشي وينزلق لأسفل مرة أخرى.
هذه هي حالة الاستخدام الأكثر شيوعًا وتعمل بشكل مثالي مع الاتجاه الافتراضي normal.
كود HTML:
<div class="content-box reveal">
<h3>Scroll Down</h3>
<p>This box animates into view.</p>
</div>
كود CSS:
@keyframes fade-and-slide-in {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal {
/* Start in the 'from' state of the animation */
opacity: 0;
animation: fade-and-slide-in linear forwards;
animation-timeline: view();
/* animation-direction: normal; is the default, so it's not needed */
}
كيف يعمل:
- نحدد إطارات رئيسية باسم
fade-and-slide-inتأخذ العنصر من الشفافية وفي موضع منخفض (translateY(50px)) إلى معتم بالكامل وفي موضعه الأصلي (translateY(0)). - نطبق هذه الرسوم المتحركة على عنصر
.reveal، والأهم من ذلك، نربطها بجدول زمنيview(). نستخدم أيضًاanimation-fill-mode: forwards;لضمان بقاء العنصر في حالته النهائية بعد اكتمال الجدول الزمني. - بما أن الاتجاه هو
normal، فعندما يبدأ العنصر في دخول منفذ العرض (تقدم الجدول الزمني > 0%)، تبدأ الرسوم المتحركة بالعمل للأمام. - أثناء التمرير لأسفل، يصبح العنصر أكثر وضوحًا، ويزداد تقدم الجدول الزمني، وتتحرك الرسوم المتحركة نحو حالتها النهائية `to`.
- إذا قمت بالتمرير لأعلى مرة أخرى، يصبح العنصر أقل وضوحًا، ويقل تقدم الجدول الزمني، ويعكس المتصفح تلقائيًا الرسوم المتحركة، مما يجعله يتلاشى وينزلق لأسفل. تحصل على تحكم ثنائي الاتجاه مجانًا!
السيناريو 2: تأثير "الترجيع" أو "إعادة التجميع"
الهدف: يبدأ العنصر في حالة مفككة أو نهائية، وأثناء التمرير لأسفل، يتحرك إلى حالته الأولية والمجمعة.
هذه حالة استخدام مثالية لـ animation-direction: reverse;. تخيل عنوانًا تبدأ فيه الحروف متناثرة وتتجمع معًا أثناء التمرير.
كود HTML:
<h1 class="title-reassemble">
<span>H</span><span>E</span><span>L</span><span>L</span><span>O</span>
</h1>
كود CSS:
@keyframes scatter-letters {
from {
/* Assembled state */
transform: translate(0, 0) rotate(0);
opacity: 1;
}
to {
/* Scattered state */
transform: translate(var(--x), var(--y)) rotate(360deg);
opacity: 0;
}
}
.title-reassemble span {
display: inline-block;
animation: scatter-letters linear forwards;
animation-timeline: view(block);
animation-direction: reverse; /* The key ingredient! */
}
/* Assign random end-positions for each letter */
.title-reassemble span:nth-child(1) { --x: -150px; --y: 50px; }
.title-reassemble span:nth-child(2) { --x: 80px; --y: -40px; }
/* ... and so on for other letters */
كيف يعمل:
- تحدد إطاراتنا الرئيسية،
scatter-letters، الرسوم المتحركة من حالة مجمعة (`from`) إلى حالة متناثرة (`to`). - نطبق هذه الرسوم المتحركة على كل `span` للحروف ونربطها بجدول زمني
view(). - نقوم بتعيين
animation-direction: reverse;. هذا يقلب التعيين. - عندما يكون العنوان خارج الشاشة (تقدم الجدول الزمني هو 0%)، يتم إجبار الرسوم المتحركة على حالتها 100% (الإطار الرئيسي `to`)، لذا تكون الحروف متناثرة وغير مرئية.
- أثناء التمرير لأسفل ودخول العنوان إلى منفذ العرض، يتقدم الجدول الزمني نحو 100%. نظرًا لأن الاتجاه معكوس، تعمل الرسوم المتحركة من إطارها الرئيسي 100% *للخلف* إلى إطارها الرئيسي 0%.
- النتيجة: تتطاير الحروف وتتجمع أثناء التمرير إلى العرض. التمرير لأعلى مرة أخرى يجعلها تتناثر من جديد.
السيناريو 3: الدوران ثنائي الاتجاه
الهدف: أيقونة ترس تدور في اتجاه عقارب الساعة عند التمرير لأسفل وعكس اتجاه عقارب الساعة عند التمرير لأعلى.
هذا تطبيق مباشر آخر للاتجاه الافتراضي normal.
كود HTML:
<div class="icon-container">
<img src="gear.svg" class="spinning-gear" alt="Spinning gear icon" />
</div>
كود CSS:
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.spinning-gear {
animation: spin linear;
/* Attach to the whole document scroll for a continuous effect */
animation-timeline: scroll(root);
}
كيف يعمل:
أثناء التمرير لأسفل في الصفحة، يتقدم الجدول الزمني لتمرير الجذر من 0% إلى 100%. مع اتجاه التحريك normal، يتم تعيين هذا مباشرة إلى الإطارات الرئيسية لـ `spin`، مما يؤدي إلى دوران الترس من 0 إلى 360 درجة (في اتجاه عقارب الساعة). عند التمرير لأعلى مرة أخرى، يقل تقدم الجدول الزمني، ويتم تشغيل الرسوم المتحركة بشكل عكسي، مما يؤدي إلى دوران الترس من 360 درجة إلى 0 درجة (عكس اتجاه عقارب الساعة). الأمر بسيط وأنيق.
تقنيات متقدمة للتحكم في التدفق
إتقان normal و reverse يمثل 90% من المعركة. ولكن لإطلاق العنان للإمكانات الإبداعية حقًا، تحتاج إلى دمج التحكم في الاتجاه مع التحكم في نطاق الجدول الزمني.
التحكم في الجدول الزمني: `animation-range`
بشكل افتراضي، يبدأ الجدول الزمني view() عندما يدخل العنصر ("الموضوع") منفذ التمرير وينتهي عندما يمر من خلاله بالكامل. تتيح لك خصائص animation-range-* إعادة تعريف نقطة البداية والنهاية هذه.
animation-range-start: [phase] [offset];
animation-range-end: [phase] [offset];
يمكن أن تكون `phase` قيمًا مثل:
entry: اللحظة التي يبدأ فيها الموضوع في دخول منفذ التمرير.cover: اللحظة التي يكون فيها الموضوع محتوى بالكامل داخل منفذ التمرير.contain: اللحظة التي يحتوي فيها الموضوع بالكامل على منفذ التمرير (للعناصر الكبيرة).exit: اللحظة التي يبدأ فيها الموضوع في مغادرة منفذ التمرير.
دعنا نحسّن مثالنا "الكشف عند التمرير". ماذا لو أردنا أن يتحرك فقط عندما يكون في منتصف الشاشة؟
كود CSS:
.reveal-in-middle {
animation: fade-and-slide-in linear forwards;
animation-timeline: view();
animation-direction: normal;
/* New additions for range control */
animation-range-start: entry 25%;
animation-range-end: exit 75%;
}
كيف يعمل:
animation-range-start: entry 25%;تعني أن الرسوم المتحركة (وجدولها الزمني) لن تبدأ في بداية مرحلة `entry`. ستنتظر حتى يكون العنصر قد قطع 25% من طريقه إلى منفذ العرض.animation-range-end: exit 75%;تعني أن الرسوم المتحركة ستعتبر مكتملة بنسبة 100% عندما يتبقى للعنصر 75% فقط من نفسه قبل الخروج بالكامل.- يؤدي هذا فعليًا إلى إنشاء "منطقة نشطة" أصغر للرسوم المتحركة في منتصف منفذ العرض. ستحدث الرسوم المتحركة بشكل أسرع وأكثر مركزية. لا يزال السلوك الاتجاهي يعمل بشكل مثالي ضمن هذا النطاق الجديد والمقيد.
التفكير في تقدم الجدول الزمني: النظرية الموحدة
إذا شعرت بالارتباك في أي وقت، فارجع إلى هذا النموذج الأساسي:
- حدد الجدول الزمني: هل تتتبع الصفحة بأكملها (
scroll()) أم رؤية عنصر (view())؟ - حدد النطاق: متى يبدأ هذا الجدول الزمني (0%) ومتى ينتهي (100%)؟ (باستخدام
animation-range). - عيّن الرسوم المتحركة: كيف يتم تعيين إطاراتك الرئيسية على تقدم الجدول الزمني من 0% إلى 100%؟ (باستخدام
animation-direction).
normal: 0% من الجدول الزمني -> 0% من الإطارات الرئيسية.reverse: 0% من الجدول الزمني -> 100% من الإطارات الرئيسية.
التمرير للأمام يزيد من تقدم الجدول الزمني. التمرير للخلف يقلل منه. كل شيء آخر ينبع من هذه القواعد البسيطة.
دعم المتصفحات، الأداء، وأفضل الممارسات
كما هو الحال مع أي تقنية ويب متطورة، من الضروري مراعاة الجوانب العملية للتنفيذ.
دعم المتصفحات الحالي
اعتبارًا من أواخر عام 2023، أصبحت الرسوم المتحركة المعتمدة على التمرير في CSS مدعومة في المتصفحات المستندة إلى Chromium (Chrome, Edge) وهي قيد التطوير النشط في Firefox و Safari. تحقق دائمًا من الموارد المحدثة مثل CanIUse.com للحصول على أحدث معلومات الدعم.
في الوقت الحالي، يجب التعامل مع هذه الرسوم المتحركة على أنها تحسين تدريجي (progressive enhancement). يجب أن يكون الموقع يعمل بشكل مثالي بدونها. يمكنك استخدام قاعدة @supports لتطبيقها فقط في المتصفحات التي تفهم الصيغة:
/* Default styles for all browsers */
.reveal {
opacity: 1;
transform: translateY(0);
}
/* Apply animations only if supported */
@supports (animation-timeline: view()) {
.reveal {
opacity: 0; /* Set initial state for animation */
animation: fade-and-slide-in linear forwards;
animation-timeline: view();
}
}
اعتبارات الأداء
أكبر مكسب لهذه التقنية هو الأداء. ومع ذلك، لا يتم تحقيق هذه الفائدة بالكامل إلا إذا قمت بتحريك الخصائص الصحيحة. للحصول على أفضل تجربة ممكنة، التزم بتحريك الخصائص التي يمكن معالجتها بواسطة خيط التركيب (compositor thread) في المتصفح ولا تؤدي إلى إعادة حساب التخطيط أو إعادة الطلاء.
- خيارات ممتازة:
transform,opacity. - استخدم بحذر:
color,background-color. - تجنب إن أمكن:
width,height,margin,top,left(الخصائص التي تؤثر على تخطيط العناصر الأخرى).
أفضل ممارسات الوصولية
تضيف الرسوم المتحركة لمسة جمالية، لكنها يمكن أن تكون مشتتة أو حتى ضارة لبعض المستخدمين، خاصة أولئك الذين يعانون من اضطرابات دهليزية. احترم دائمًا تفضيلات المستخدم.
استخدم استعلام الوسائط prefers-reduced-motion لتعطيل أو تخفيف الرسوم المتحركة الخاصة بك.
@media (prefers-reduced-motion: reduce) {
.reveal, .spinning-gear, .title-reassemble span {
animation: none;
opacity: 1; /* Ensure elements are visible by default */
transform: none; /* Reset any transforms */
}
}
علاوة على ذلك، تأكد من أن الرسوم المتحركة زخرفية ولا تنقل معلومات مهمة لا يمكن الوصول إليها بطريقة أخرى.
الخاتمة
تمثل الرسوم المتحركة المعتمدة على التمرير في CSS نقلة نوعية في كيفية بناء واجهات ويب ديناميكية. من خلال نقل التحكم في الرسوم المتحركة من JavaScript إلى CSS، نكتسب فوائد أداء هائلة وقاعدة تعليمات برمجية أكثر تعريفية وقابلية للصيانة.
يكمن مفتاح إطلاق إمكاناتها الكاملة في فهم وإتقان التحكم في التدفق. من خلال إعادة تصور خاصية animation-direction ليس كأداة تحكم في التكرار، ولكن كأداة لربط تقدم الجدول الزمني بتقدم الرسوم المتحركة، نكتسب تحكمًا ثنائي الاتجاه دون عناء. يوفر السلوك الافتراضي normal النمط الأكثر شيوعًا - التحريك للأمام عند التمرير للأمام وللخلف عند التمرير العكسي - بينما يمنحنا reverse القدرة على إنشاء تأثيرات "تراجع" أو "ترجيع" مقنعة.
مع استمرار نمو دعم المتصفحات، ستنتقل هذه التقنيات من كونها تحسينًا تدريجيًا إلى مهارة أساسية لمطوري الواجهات الأمامية الحديثين. لذا ابدأ في التجربة اليوم. أعد التفكير في تفاعلاتك القائمة على التمرير، وانظر كيف يمكنك استبدال JavaScript المعقدة ببضعة أسطر من CSS الأنيقة وعالية الأداء والمدركة للاتجاه.