البرمجة متوسط 9 دقيقة

كيفية تحريك العناصر عند التمرير

الحركات المُشغَّلة بالتمرير — حيث تتلاشى العناصر أو تنزلق للداخل عند دخولها نافذة العرض — هي من أكثر تفاصيل واجهة المستخدم فاعلية التي يمكنك إضافتها. حين تُبنى بشكل صحيح تكون سريعة وسهلة الوصول ولا تتطلب سوى القليل من الكود. يغطي هذا الدليل نهج IntersectionObserver والبديل الحديث الخالص بـ CSS.

الخطوات

  1. 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. 2

    تعيين الحالة الافتراضية المخفية في CSS

    عيّن كل عنصر [data-animate] على حالته "قبل" — غير مرئي ومزاح قليلاً للأسفل. الانتقال مُعرَّف هنا، لذا يتولى CSS كل عمل الحركة بمجرد تغيير الكلاس.

    css
    [data-animate] {
      opacity: 0;
      transform: translateY(20px);
      transition: opacity 0.5s ease, transform 0.5s ease;
    }
  3. 3

    تعريف الحالة المرئية بـ .is-in

    حين يملك عنصر كلاس .is-in، يكون مرئياً بالكامل في موضعه الطبيعي. الانتقال المُعرَّف على الحالة الأساسية يُشغَّل تلقائياً حين يُضاف هذا الكلاس.

    css
    [data-animate].is-in {
      opacity: 1;
      transform: translateY(0);
    }
  4. 4

    استخدام IntersectionObserver لإضافة الكلاس

    IntersectionObserver يُشغّل callback حين يتقاطع عنصر مع حد رؤية — لا مستمع لأحداث التمرير، ولا قراءات لـ layout، ولا توقف. بمجرد أن يصبح العنصر مرئياً، أوقف مراقبته حتى لا يُعاد تحريكه حين يمرر المستخدم للأعلى.

    javascript
    const 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. 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. 6

    إضافة Fallback أنيق للمتصفحات القديمة

    IntersectionObserver مدعوم في جميع المتصفحات الحديثة. في البيئات القديمة النادرة التي لا تدعمه، أظهر جميع العناصر فوراً بدلاً من ترك الصفحة فارغة دائماً.

    javascript
    if (!('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. 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. 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 فوقه للمتصفحات الداعمة، وستحصل على حل مقاوم للمستقبل يزداد سرعة تدريجياً مع نضج دعم المتصفحات.

#CSS #Animation #IntersectionObserver
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.