视频播放进度,换页后咋能接着看?聊聊跨页面状态保持的骚操作

2,277字
10–14 分钟
in

网站里看个视频教程,点开新页面查资料再回来,进度条直接归零,这种抓狂时刻谁没经历过?其实这事儿完全有解,而且不用啥黑科技,靠浏览器自带的那点东西就能整得明明白白。

目录

啥是视图过渡

视图过渡这词听着玄乎,说白了就是页面切换时的动画效果。以前想搞这种丝滑效果,基本得靠单页应用框架硬撑。现在原生CSS的视图过渡特性,能让页面跳转时把旧页面的内容“变”成新页面的一部分,视觉上连成一片。但这里有个坑——视频这种有状态的东西,光靠CSS搞不定,得让浏览器记住视频播到哪儿了。

保存视频状态

核心思路其实特简单:跳页前把视频的当前时间存起来,新页面加载时再读出来塞回去。浏览器里有个叫sessionStorage的地儿,专门用来存这种临时数据,页面关了就自动清,正合适。

// 监听页面即将跳转
window.addEventListener('pageswap', (event) => {
  const video = document.querySelector('video');
  if (video) {
    const videoState = {
      currentTime: video.currentTime,
      paused: video.paused,
      volume: video.volume
    };
    sessionStorage.setItem('videoState', JSON.stringify(videoState));
  }
});

这里要注意,pageswap事件在页面开始切换时触发,时间点掐得刚刚好。要是用beforeunload也不是不行,但那个触发时机有时候会跟新页面的加载打架,导致存了个寂寞。另外存数据时得用JSON.stringify包一下,不然对象存进去就变成[object Object]这种废柴东西。

恢复视频状态

新页面登场时,得把之前存的宝贝翻出来用。pagereveal事件就是干这活儿的,它在新页面内容刚出来还没完全展示的时候触发,这时候改视频播放位置,用户基本感觉不到跳帧。

window.addEventListener('pagereveal', (event) => {
  const savedState = sessionStorage.getItem('videoState');
  if (savedState) {
    const video = document.querySelector('video');
    if (video) {
      const state = JSON.parse(savedState);
      video.currentTime = state.currentTime;
      if (!state.paused) {
        video.play();
      }
      video.volume = state.volume;
    }
    sessionStorage.removeItem('videoState');
  }
});

恢复完之后顺手把sessionStorage里的数据清掉,免得后面莫名其妙又被读一次。这招对音频、内嵌的iframe视频同样管用,只要是能控制播放进度的媒体元素,基本都能这么搞。

备胎方案

要是觉得pageswappagereveal这套组合拳太新,担心老浏览器不认账,可以换个稳一点的打法。

// 方案二:用pagehide和pageshow兜底
window.addEventListener('pagehide', () => {
  const video = document.querySelector('video');
  if (video) {
    sessionStorage.setItem('videoCurrentTime', video.currentTime);
    sessionStorage.setItem('videoPaused', video.paused);
  }
});

window.addEventListener('pageshow', () => {
  const savedTime = sessionStorage.getItem('videoCurrentTime');
  if (savedTime !== null) {
    const video = document.querySelector('video');
    if (video) {
      video.currentTime = parseFloat(savedTime);
      const wasPaused = sessionStorage.getItem('videoPaused') === 'true';
      if (!wasPaused) {
        video.play();
      }
    }
    sessionStorage.removeItem('videoCurrentTime');
    sessionStorage.removeItem('videoPaused');
  }
});

这套方案的缺点在于,pagehide触发时页面还没完全卸载,但新页面加载时可能会有极短的音频中断,视频会闪一下。不过对于大部分普通场景来说,这点瑕疵完全可以接受,毕竟总比进度丢了强。

避坑指南

实际操作时容易踩几个雷。一个是存状态时如果视频还没加载完,currentTime可能是个假值,最好等视频的loadedmetadata事件触发后再读。另一个是多个视频的情况,存的时候得区分开,比如用视频的id或者src做key,不然两个视频会互相覆盖。

还有个细节,有些视频源是分段加载的(m3u8这类),直接改currentTime可能跳不准,得看播放器本身支不支持seek操作。这时候就得结合播放器自己的API来搞,比如用video.js或者plyr这类库,它们都有现成的状态保存方法。