Scrollytelling

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

كيفية الإعداد

لإنشاء تجربة سرد قصصي تفاعلي، تحتاج إلى مجموعتين من العناصر: مجموعة لتتبُّع موضع الانتقال ومجموعة أخرى لتحريكها.

أولاً، حدِّد view-timeline مسمّى على العناصر التي تريد تتبُّعها. ستعمل هذه كعوامل تشغيل للصور المتحركة.

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

بعد ذلك، طبِّق الصور المتحركة على العناصر التي تريد تحريكها واربطها بالمخططات الزمنية التي أنشأتها للتو باستخدام السمة animation-timeline.

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

لكي يتمكّن animation-timeline من الإشارة إلى المخططات الزمنية المُسمّاة، يجب أن تكون في النطاق نفسه. يمكنك استخدام السمة timeline-scope في عنصر رئيسي مشترك لإتاحة المخططات الزمنية لجميع العناصر التي تحتاج إليها. غالبًا ما يكون العنصر :root خيارًا جيدًا لذلك.

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

أخيرًا، يمكنك استخدام السمة animation-range لتحديد النطاق الدقيق للمخطط الزمني الذي يجب تشغيل الحركة خلاله. يمنحك هذا تحكمًا دقيقًا في وقت تشغيل الرسوم المتحركة وكيفية تقدمها.

#animated section {
  animation-range: entry 25% cover 50%, exit 50% exit 75%;
}

مثال على الرمز

html {
  timeline-scope: --tl-1, --tl-2, --tl-3, --tl-4, --tl-5;
}

#tracked {
  section:nth-child(1){ view-timeline: --tl-1 block; }
  section:nth-child(2){ view-timeline: --tl-2 block; }
  section:nth-child(3){ view-timeline: --tl-3 block; }
  section:nth-child(4){ view-timeline: --tl-4 block; }
  section:nth-child(5){ view-timeline: --tl-5 block; }
}

@keyframes animate-in {
  from { scale: 0.5; opacity: 0; transform: rotateY(-180deg); }
  to { transform: rotateY(0deg); }
}
@keyframes animate-out {
  to { translate: 100% 0; opacity: 0; }
}

#animated {
  section {
    animation: animate-in auto linear both, animate-out auto linear forwards;
    animation-range: entry 25% cover 50%, exit 50% exit 75%;
    backface-visibility: hidden;
  }

  section:nth-child(1){ animation-timeline: --tl-1; }
  section:nth-child(2){ animation-timeline: --tl-2; }
  section:nth-child(3){ animation-timeline: --tl-3; }
  section:nth-child(4){ animation-timeline: --tl-4; }
  section:nth-child(5){ animation-timeline: --tl-5; }
}

/* MANDATORY Copy-Paste Safety: Disable continuous storytelling motion for sensitive users */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }
}

أفضل الممارسات

عند استخدام الصور المتحركة المستندة إلى الانتقال، من المهم اتّباع بعض أفضل الممارسات لضمان تجربة سلسة وسهلة الاستخدام:

  • يجب تضمين ميزة رصد الميزات: لا تتيح بعض المتصفّحات استخدام الصور المتحركة المستندة إلى الانتقال للأعلى أو للأسفل. استخدِم @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) للتحقّق من توفّر الدعم وتوفير خيار احتياطي للمتصفّحات التي لا تتيح هذه الميزة.
    • يجب تضمين عملية التحقّق من (animation-range: 0% 100%) هنا لفلترة المتصفّحات التي توفّر دعمًا جزئيًا فقط.
    • لا تستخدِم حزمة scroll-timeline-polyfill لاستراتيجية الاحتياط لأنّها لا تتضمّن جميع الميزات وتحتوي على الكثير من المشاكل المعروفة.
    • إذا كانت الحركة تُعتبر زخرفية فقط، فاختَر "التحسين التدريجي" ولا تقدّم بديلاً.
  • يجب احترام الإعدادات المفضّلة للمستخدمين: يفضّل بعض المستخدمين تقليل الحركة على الويب. استخدِم الاستعلام عن الوسائط prefers-reduced-motion لإيقاف الصور المتحركة أو تقليلها لهؤلاء المستخدمين.
  • يُرجى محاولة تحريك خصائص CSS ذات الأداء العالي فقط: للحصول على صور متحركة أكثر سلاسة، التزِم بتحريك الخصائص التي يمكن معالجتها من خلال سلسلة عمليات التجميع في المتصفّح، مثل transform وopacity. قد تؤدي إضافة تأثيرات حركية إلى سمات أخرى، مثل width أو height، إلى حدوث مشاكل في الأداء.
  • يجب استخدام ترتيب التعريف الصحيح: عند استخدام السمة المختصرة animation، يجب تعريف animation-timeline وanimation-range بعدها لمنع السمة المختصرة من إعادة ضبط المخطط الزمني.

عند استخدام السمة view-timeline لإنشاء صورة متحركة مستندة إلى الانتقال للأعلى أو للأسفل:

  • يجب استخدام <dashed-ident> لخدمة CSS في الاسم.
  • اختياري تحديد المحور الذي تريد تتبُّعه: في حال عدم استهداف محور block التلقائي (مثلاً في شريط تمرير أفقي)، حدِّد المحور الذي تريد تتبُّعه باستخدام view-timeline-axis.
  • يجب التأكّد من أنّ نطاق البحث يعمل: عندما لا يكون العنصر الذي يعرّف view-timeline عنصرًا رئيسيًا في الشجرة المسطّحة للعنصر المتحرّك، يمكنك رفع مستوى ظهور اسم view-timeline باستخدام timeline-scope على عنصر رئيسي مشترك.

استراتيجيات احتياطية

بالنسبة إلى المتصفّحات التي لا تتيح استخدام الصور المتحركة المستندة إلى الانتقال للأعلى أو للأسفل، يمكنك استخدام خيار احتياطي لإعادة إنشاء التأثيرات المرئية. يتم عادةً إنشاء الإجراءات الاحتياطية باستخدام أداة استماع للتمرير (لتأثيرات ScrollTimeline) أو واجهة برمجة التطبيقات IntersectionObserver (لتأثيرات ViewTimeline).

في المتصفّحات التي تتضمّن دعمًا مدمجًا للصور المتحركة المستندة إلى الانتقال، استخدِم دائمًا تنفيذ CSS الأصلي لأنّها أكثر فعالية.

يُرجى العِلم أنّه لا يمكن إعادة إنشاء كل تأثير باستخدام نهج الإجراءات الاحتياطية.

بالنسبة إلى حالة الاستخدام هذه تحديدًا، يطبّق النص البرمجي التالي الإجراء الاحتياطي للمتصفّحات التي لا تتيح استخدام الصور المتحركة المستندة إلى الانتقال للأعلى أو للأسفل. يستخدم هذا الرمز واجهة برمجة التطبيقات IntersectionObserver لتتبُّع مستوى ظهور كل عنصر #tracked section ويعدّل السمة transform لسمة #animated section المقابلة وفقًا لذلك.

const animatedSections = document.querySelectorAll('#animated section');

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const sectionIndex = Array.from(document.querySelectorAll('#tracked section')).indexOf(entry.target);
    if (sectionIndex !== -1) {
      const animatedSection = animatedSections[sectionIndex];
      const ratio = entry.intersectionRatio;

      // Animate-in
      animatedSection.style.opacity = ratio;
      animatedSection.style.transform = `scale(${0.5 + ratio * 0.5}) rotateY(${-180 + ratio * 180}deg)`;

      // Animate-out
      if (ratio < 0.5) {
        animatedSection.style.translate = `${(0.5 - ratio) * 2 * 100}% 0`;
      } else {
        animatedSection.style.translate = '0 0';
      }
    }
  });
}, { threshold: Array.from({length: 101}, (_, i) => i / 100) });

document.querySelectorAll('#tracked section').forEach(section => {
  observer.observe(section);
});

وملف CSS المصاحب:

#animated section {
  opacity: 0;
  transform: scale(0.5)  rotateY(-180deg);
  backface-visibility: hidden;
}

/* MANDATORY Copy-Paste Safety: Ensure content remains fully visible and legible for assistive technologies or users with motion sensitivities */
@media (prefers-reduced-motion: reduce) {
  #animated section {
    opacity: 1 !important;
    transform: none !important;
    translate: 0 0 !important;
  }
}

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