Aviv
פורסם: 13 באוגוסט 2025עודכן: 20 בינואר 2026

יצירת אנימציות Video-on-Scroll חלקות: ממדיה וידאו לשליטה מלאה בפריימים

אנימציות Video-on-Scroll דורשות שליטה מלאה בפריימים ובביצועים. המאמר מפרט על מדוע HTML5 Video אינו מספיק ברוב המקרים, ואיך עבודה עם Frame Sequences ו־Canvas מאפשרת אנימציות חלקות, יציבות ומדויקות. מדריך עקרוני לבחירת הארכיטקטורה הנכונה.

יצירת אנימציות Video-on-Scroll חלקות: ממדיה וידאו לשליטה מלאה בפריימים

יצירת אנימציות Video-on-Scroll חלקות: ממדיה וידאו לשליטה מלאה בפריימים

אנימציות המסונכרנות לגלילת המשתמש הפכו בשנים האחרונות לאחד הכלים המשמעותיים ביותר ליצירת חוויית משתמש מתקדמת. בניגוד לאנימציה לינארית שמתרחשת ללא תלות במשתמש, אנימציית Scroll מאפשרת שליטה מלאה בקצב, בכיוון ובחוויה הכוללת. המשתמש אינו צופה בתנועה, אלא מפעיל אותה בעצמו.

עם זאת, הבחירה בטכנולוגיה הנכונה ליישום אנימציות כאלה היא קריטית. פתרון שנראה פשוט בתחילת הדרך עלול להתגלות כבעייתי במובייל, לא עקבי בין דפדפנים, או מוגבל מבחינת שליטה וביצועים.

במאמר זה נסקור את שתי הגישות המרכזיות ליישום אנימציות Video-on-Scroll, נבין מדוע שימוש ב־HTML5 Video לרוב אינו מספק, ונראה כיצד עבודה עם Frame Sequences ו־Canvas מספקת פתרון מדויק, יציב וסקיילבילי יותר.

הגישה הראשונית: HTML5 Video מסונכרן לגלילה

הפתרון האינטואיטיבי הראשון הוא שימוש באלמנט <video> והתקדמות בזמן הניגון בהתאם למיקום הגלילה. ספריות כמו GSAP ו־ScrollTrigger מאפשרות חיבור ישיר בין scroll progress לבין currentTime של הווידאו.

דוגמה טיפוסית למימוש כזה נראית כך:

export default class VideoScene extends Section {
  private video: HTMLVideoElement;
  private scrollTrigger: ScrollTrigger;

  setupVideoScroll() {
    this.scrollTrigger = ScrollTrigger.create({
      trigger: '.video-container',
      start: 'top top',
      end: 'bottom bottom',
      scrub: true,
      onUpdate: (self) => {
        const duration = this.video.duration;
        this.video.currentTime = self.progress * duration;
      },
    });
  }
}

לגישה הזו יש יתרונות ברורים. הדפדפנים תומכים בניגון וידאו בצורה מובנית, מדובר בקובץ אחד במקום מאות נכסים, וקודקי וידאו מספקים דחיסה טובה יחסית.

הבעיות האמיתיות עם וידאו בגלילה

בפועל, כאשר מנסים ליישם אנימציה מדויקת שמגיבה לגלילה, מתגלות מגבלות מהותיות:

  • קפיצות וגמגומים, בעיקר במובייל ובמכשירים חלשים
  • מגבלות autoplay בדפדפנים מודרניים
  • חוסר דיוק בקפיצה אחורה וקדימה בין פריימים
  • איבוד איכות עקב דחיסה
  • חוסר שליטה בפריים בודד

וידאו נועד לניגון רציף. הוא לא נבנה לשליטה פריימית מדויקת. ברגע שהחוויה דורשת שליטה מלאה, הווידאו הופך לגורם מגביל.

המעבר ל-Frame Sequences

Frame Sequence הוא רצף של תמונות סטטיות, שכל אחת מהן מייצגת פריים אחד. הצגת הפריימים ברצף מהיר יוצרת אשליית תנועה, אך בניגוד לווידאו, כאן השליטה היא מוחלטת.

כל פריים נטען, נשמר בזיכרון, ומוצג בדיוק ברגע הרצוי.

הפקת פריימים מווידאו מקור

תהליך העבודה מתחיל באופליין. לוקחים סרטון מקור ומפרקים אותו לפריימים באמצעות FFmpeg.

דוגמה לתהליך אוטומטי:

ffmpeg -i "video/source.mp4" -vf "fps=30" "frames/frame_%03d.png"

לאחר מכן ממירים את הפריימים לפורמט WebP כדי לצמצם משקל:

ffmpeg -i "frames/frame_%03d.png" -c:v libwebp -quality 80 "webp/frame_%03d.webp"

כך מתקבל רצף פריימים קל יחסית, חד וניתן לשליטה מלאה.

התאמה למכשירים שונים

אנימציה שמיועדת לדסקטופ אינה מתאימה בהכרח למובייל. לכן נהוג לייצר מספר רצפים:

  • דסקטופ עם מספר פריימים גבוה לאנימציה חלקה במיוחד
  • מובייל עם פחות פריימים לצמצום זמן טעינה

המערכת בוחרת בזמן ריצה את הרצף המתאים בהתאם ל־breakpoint.

אתגר הביצועים: טעינת מאות פריימים

טעינה נאיבית של מאות תמונות תגרום לחסימת ממשק המשתמש. הפתרון הוא טעינה מדורגת:

  • טעינה מיידית של מספר פריימים ראשונים
  • הצגת פריים ראשון מיד
  • טעינת שאר הפריימים ברקע

דוגמה לטעינה מדורגת:

await this.preloadFrames(1, initialFrameCount);
this.renderFrame(1);
this.loadFramesInBackground();

ולטעינה מקבילית ברקע:

private loadFramesInBackground() {
  const queue = new ParallelQueue();

  for (let i = preloadCount; i <= totalFrames; i++) {
    queue.enqueue(async () => {
      const img = await this.loadFrame(i);
      this.frameCache.set(i, img);
    });
  }

  queue.start();
}

רינדור באמצעות Canvas

רינדור הפריימים נעשה באמצעות <canvas>, ולא באמצעות <img>. הבחירה ב־Canvas מאפשרת שליטה מלאה בפריסה, ביצועים גבוהים, והתאמה למסכים עם DPI גבוה.

דוגמה לרינדור פריים:

private renderFrame(frameNumber: number) {
  const img = this.frameCache.get(frameNumber);
  if (!img || !this.ctx) return;

  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

  const pixelRatio = window.devicePixelRatio || 1;
  const canvasRatio = this.canvas.width / this.canvas.height;
  const imageRatio = img.width / img.height;

  let drawWidth = this.canvas.width;
  let drawHeight = this.canvas.height;
  let offsetX = 0;
  let offsetY = 0;

  if (canvasRatio > imageRatio) {
    drawHeight = drawWidth / imageRatio;
    offsetY = (this.canvas.height - drawHeight) / 2;
  } else {
    drawWidth = drawHeight * imageRatio;
    offsetX = (this.canvas.width - drawWidth) / 2;
  }

  this.ctx.drawImage(
    img,
    offsetX,
    offsetY,
    drawWidth / pixelRatio,
    drawHeight / pixelRatio
  );
}

סנכרון בין גלילה לפריימים

כעת מיפוי הגלילה פשוט: מחשבים התקדמות, מתרגמים אותה לאינדקס פריים, ומרנדרים אותו.

const frame = Math.round(progress * totalFrames);
this.renderFrame(frame);

אופטימיזציות מתקדמות

כדי להגיע לחוויה חלקה באמת, מוסיפים שכבות חכמות:

  • טעינת פריימים קדימה או אחורה לפי כיוון הגלילה
  • הגבלת טווח טעינה לפריימים רלוונטיים
  • מעבר חלק בין אנימציה לולאתית ל־scroll-based

יתרונות מול חסרונות

יתרונות
שליטה מלאה בפריימים, ביצועים יציבים, איכות ויזואלית גבוהה, חוויית משתמש מדויקת וגמישות עיצובית מלאה.

חסרונות
מורכבות פיתוח גבוהה יותר, ריבוי נכסים ולוגיקת טעינה מתקדמת.

בפרויקטים שבהם החוויה היא חלק מהמסר, מדובר במחיר מוצדק.

סיכום

אנימציות Video-on-Scroll אינן קישוט. הן שכבת חוויה.
וידאו רגיל מתאים לניגון, לא לשליטה.
כאשר נדרשים דיוק, יציבות וביצועים עקביים, עבודה עם Frame Sequences ו־Canvas היא בחירה הנדסית נכונה.

ב- Prexia אנחנו מתייחסים לפתרונות כאלה כתשתית חווייתית ולא כטריק ויזואלי. מי שבונה אותן נכון יוצר חוויה שהמשתמש זוכר.

מאמרים קשורים

לחץ Alt+א כדי לפתוח או לסגור לוח זה, או השתמש ב-Alt+1 עד Alt+5 לשינוי מהיר של גודל הגופן