الخطوات
-
1
تمييز العناصر بـ data-animate
أضف السمة
data-animateإلى أي عنصر تريد تحريكه. يبقي هذا التوصيف نظيفاً دلالياً ويمنح JavaScript محدداً مستهدفاً لا يتعارض مع تنسيق الكلاسات.html<section data-animate> <h2>Section Title</h2> <p>Content that fades in on scroll.</p> </section> <div class="card" data-animate>...</div> <img src="photo.jpg" alt="..." data-animate> -
2
تعيين الحالة الافتراضية المخفية في CSS
عيّن كل عنصر
[data-animate]على حالته "قبل" — غير مرئي ومزاح قليلاً للأسفل. الانتقال مُعرَّف هنا، لذا يتولى CSS كل عمل الحركة بمجرد تغيير الكلاس.css[data-animate] { opacity: 0; transform: translateY(20px); transition: opacity 0.5s ease, transform 0.5s ease; } -
3
تعريف الحالة المرئية بـ .is-in
حين يملك عنصر كلاس
.is-in، يكون مرئياً بالكامل في موضعه الطبيعي. الانتقال المُعرَّف على الحالة الأساسية يُشغَّل تلقائياً حين يُضاف هذا الكلاس.css[data-animate].is-in { opacity: 1; transform: translateY(0); } -
4
استخدام IntersectionObserver لإضافة الكلاس
IntersectionObserver يُشغّل callback حين يتقاطع عنصر مع حد رؤية — لا مستمع لأحداث التمرير، ولا قراءات لـ layout، ولا توقف. بمجرد أن يصبح العنصر مرئياً، أوقف مراقبته حتى لا يُعاد تحريكه حين يمرر المستخدم للأعلى.
javascriptconst observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('is-in'); observer.unobserve(entry.target); // animate once, then stop watching } }); }, { threshold: 0.15 } // trigger when 15% of the element is visible ); document.querySelectorAll('[data-animate]').forEach((el) => { observer.observe(el); }); -
5
احترام تفضيل prefers-reduced-motion
بعض المستخدمين لديهم حساسية تجاه الحركة وضبطوا نظام التشغيل على تقليل الرسوم المتحركة. اكتشف هذا التفضيل واتجاهل الحركة كلياً — اجعل كل عنصر مرئياً فوراً بدلاً من إخفائه ثم الكشف عنه.
javascript// Check the preference before setting up any animations const prefersReduced = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches; if (prefersReduced) { // Make all elements immediately visible — no animation document.querySelectorAll('[data-animate]').forEach((el) => { el.classList.add('is-in'); }); } else { // Set up the observer as normal const observer = new IntersectionObserver(/* ... */); document.querySelectorAll('[data-animate]').forEach((el) => observer.observe(el)); } -
6
إضافة Fallback أنيق للمتصفحات القديمة
IntersectionObserver مدعوم في جميع المتصفحات الحديثة. في البيئات القديمة النادرة التي لا تدعمه، أظهر جميع العناصر فوراً بدلاً من ترك الصفحة فارغة دائماً.
javascriptif (!('IntersectionObserver' in window)) { // No IntersectionObserver — show everything immediately document.querySelectorAll('[data-animate]').forEach((el) => { el.classList.add('is-in'); }); } else { // Modern path: set up observer } -
7
استخدام Scroll-Driven Animations كبديل حديث
واجهة برمجة CSS
animation-timeline: view()تربط الحركة مباشرة بمقدار ظهور العنصر في نافذة العرض — بدون JavaScript على الإطلاق. استخدم@supportsلإضافتها فوق Fallback IntersectionObserver. مدعومة في Chromium 115+ وFirefox 110+.css@supports (animation-timeline: view()) { [data-animate] { /* override the JS-dependent approach */ opacity: 0; transform: translateY(20px); animation: fade-up linear forwards; animation-timeline: view(); animation-range: entry 0% entry 30%; } @keyframes fade-up { to { opacity: 1; transform: translateY(0); } } } -
8
تأخير متسلسل لعناصر متعددة للمزيد من الأناقة
عند تحريك عناصر أشقاء متعددة (شبكة بطاقات، قائمة ميزات)، يبدو تأخير بسيط متسلسل بين كل عنصر أكثر احترافية بكثير من إطلاقها جميعاً في وقت واحد. استخدم خاصية CSS مخصصة تُضبط inline من JavaScript.
javascript// Set a stagger delay on each observed element document.querySelectorAll('[data-animate]').forEach((el, index) => { el.style.setProperty('--stagger', `${index * 80}ms`); observer.observe(el); });
نصائح ومحاذير
- اضبط <code>transition-delay: var(--stagger, 0ms)</code> على <code>[data-animate]</code> في CSS لكي يُستهلك متغير التأخير تلقائياً دون أي JavaScript إضافي.
- ابقِ الحركات قصيرة — 400–600ms هي النقطة المثالية. أي شيء فوق 700ms يبدو بطيئاً ويجعل المستخدمين ينتظرون المحتوى.
- حرّك <code>opacity</code> و<code>transform</code> فقط. لا تحرّك أبداً <code>height</code> أو <code>top</code> أو <code>left</code> أو <code>margin</code> — هذه تُشغّل إعادة حساب التخطيط وتسبب التوقف.
- العناصر قرب أعلى الصفحة قد تكون موجودة بالفعل في نافذة العرض عند التحميل. شغّل callback المراقب مرة واحدة فور تحميل الصفحة أو أضف تلك العناصر إلى <code>.is-in</code> افتراضياً.
خاتمة
نمط IntersectionObserver — data-animate + CSS transition + observer + unobserve — هو النهج الجاهز للإنتاج اليوم. يحتاج أقل من 20 سطراً من JavaScript، يعمل دون مستمعي تمرير، ويعمل في كل متصفح حديث. أضف CSS scroll-driven animation API فوقه للمتصفحات الداعمة، وستحصل على حل مقاوم للمستقبل يزداد سرعة تدريجياً مع نضج دعم المتصفحات.