用 scroll 控制一條 AI 影片:由頭 scrub 到尾嘅 landing
我想試一件事:唔用 play 掣,淨係靠 scroll 控制一條影片由頭行到尾。 你向下捲幾多,影片就 seek 到嗰個時間點;停低就定格。揀咗一條由 AI 生成、介紹地雷系(jirai-kei)次文化嘅短片做題目,分 6 章,配繁中+日文字幕,用 Lenis 做 smooth scroll。
👉 撳呢度睇 live 版面 ── 慢慢 scroll 就見到影片跟手 scrub。
Scroll 進度 = 影片時間線
整個頁面得一條影片 pin 喺畫面中央,後面留 700vh 嘅捲動長度做「scrub 軌道」。每一幀我攞捲動進度 p(由 0 到 1):
const total = scrollEl.offsetHeight - innerHeight; // 可捲動距離
const p = clamp(-scrollEl.getBoundingClientRect().top / total, 0, 1);
再將 p 乘影片長度,就係要 seek 去嘅時間點。冇用任何 timeline library、冇 play/pause/reverse ── 狀態完全由 scroll 位置決定,你倒返 scroll 上去,影片自然倒返。
緩動 seek 先至 scrub 得滑
如果直接 film.currentTime = p * duration,scrub 會一格格跳、好生硬。我改成每幀向目標緩動(lerp)行近一啲:
const target = p * film.duration;
currentTime += (target - currentTime) * 0.14; // 0.14 ≈ 順滑度
if (film.readyState >= 2) film.currentTime = clamp(currentTime, 0, duration - 0.04);
呢個 0.14 係順滑同跟手之間嘅平衡:大咗太跳、細咗太「拖」。配埋 Lenis 嘅 smooth wheel,捲動本身已經夠順,影片就跟得似「揸住個時間軸」多過「拖緊一條 scrubber」。
6 章字幕點同影片對位
字幕唔係連續變,而係離散切章:用 Math.floor(p * 6) 將進度切成 6 段,每段 toggle 對應 .cap 同底部章節點嘅 on class;頂部進度條闊度直接 = p × 100%。所以同一個 p 同時驅動三樣嘢 ── 影片時間、當前章節、進度條 ── 三者永遠對得返。
學到嘅嘢
1. 要 scrub 影片,先要 buffer 到。 <video> 加 preload="auto" + playsinline muted,先有得即時 seek;手機上要成條片 buffer 夠先 scrub 得順,所以單一條 720p、30 秒嘅片(一個 file)比逐章切片更易管。
2. Poster + preloader 防白屏。 影片未 ready 嗰陣顯示第 1 章 poster(poster="ch1.jpg"),再喺 canplay 或 load 後收起 preloader,唔會見到一忽黑。
3. 一定要照顧 prefers-reduced-motion。 開咗減動嘅人:跳過 Lenis、currentTime 直接跟(唔做 0.14 緩動)── 影片仍然跟 scroll,但冇額外慣性同平滑動畫。
4. scrub 流暢度同清晰度係 trade-off。 seek 一條太大嘅片,慢機會卡;太細又冇細節。今次揀 720p 單片,清晰度同 scrub 順滑度之間取個平衡。
總結
由一句「淨用 scroll 控制一條影片」,到一頁影片跟手 scrub、6 章字幕順住浮現 ── 最大體會係:當你將影片嘅時間軸同 scroll 綁實,觀眾就由「睇緊一條片」變成「揸住條時間軸」。 佢控制節奏,但每一個捲動位你都對到幀。