html video懒加载


一、最简单但有效:preload="none"

<video controls preload="none" poster="cover.jpg">
  <source src="demo.mp4" type="video/mp4">
</video>

特点

  • ❌ 页面加载时 不下载视频

  • ✅ 点击播放才请求

  • 👍 零 JS

  • 👎 滚动到可视区不会自动加载

👉 适合:用户主动点播放的场景


二、推荐方案:IntersectionObserver 懒加载(通用)

HTML(不直接给 src)

<video
  class="lazy-video"
  controls
  muted
  playsinline
  poster="cover.jpg"
  data-src="demo.mp4">
</video>

JS

<script>
const videos = document.querySelectorAll('.lazy-video')

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const video = entry.target
      video.src = video.dataset.src
      video.load() // 开始加载
      observer.unobserve(video)
    }
  })
}, {
  rootMargin: '100px' // 提前加载
})

videos.forEach(v => observer.observe(v))
</script>

优点

  • 👍 真正“滚动到才加载”

  • 👍 可控提前量

  • 👍 静态页面最佳实践


三、自动播放视频懒加载(常见于官网)

⚠️ 浏览器限制:必须 muted 才能自动播放

<video
  class="lazy-video"
  muted
  autoplay
  loop
  playsinline
  poster="cover.jpg"
  data-src="bg.mp4">
</video>
if (entry.isIntersecting) {
  video.src = video.dataset.src
  video.play().catch(() => {})
}

📌 常用于:

  • 首页背景视频

  • 产品展示动效

四、多 source 视频(兼容格式)

<video class="lazy-video" poster="cover.jpg">
  <source data-src="demo.mp4" type="video/mp4">
  <source data-src="demo.webm" type="video/webm">
</video>
video.querySelectorAll('source').forEach(s => {
  s.src = s.dataset.src
})
video.load()

五、离开视口自动暂停(加分项)

const observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    const video = entry.target
    if (entry.isIntersecting) {
      video.play().catch(()=>{})
    } else {
      video.pause()
    }
  })
})

👉 对 长页面多个视频 非常重要


六、移动端 & iOS 必备属性

<video
  muted
  playsinline
  webkit-playsinline>
</video>

否则:

  • iOS 会强制全屏

  • 自动播放失败

七、推荐配置速查表

场景 推荐方案
点了才播 preload="none"
滚动加载 IntersectionObserver
自动播放 muted + autoplay + IO
背景视频 懒加载 + 视口暂停
多视频页面 离屏 pause

 

八、完整「生产级」示例(可直接用)懒加载+封面替换

function videoLazyLoad() {
  const io = new IntersectionObserver(entries => {
    entries.forEach(e => {
      const v = e.target;

      if (e.isIntersecting) {
        if (!v.src) {
          v.src = v.dataset.src;
        }
        v.play().catch(() => {});
      } else {
        v.pause();
      }
    });
  }, { rootMargin: '150px' });

  document.querySelectorAll('.lazy-video').forEach(v => io.observe(v));
}

$(function () {
  videoLazyLoad();
});

一句话总结

video 懒加载 = 不给 src + IntersectionObserver + 可视区加载 / 离屏暂停

 

防抖(规避快速掠过):

function videoLazyLoad() {
  const timers = new WeakMap();

  const io = new IntersectionObserver(entries => {
    entries.forEach(e => {
      const v = e.target;

      if (e.isIntersecting) {
        // 已经在计时了就不重复设置
        if (timers.has(v)) return;

        const t = setTimeout(() => {
          if (!v.src) v.src = v.dataset.src;
          v.play().catch(() => {});
          timers.delete(v);
        }, 300); // 👈 停留 300ms 才加载

        timers.set(v, t);
      } else {
        // 快速掠过:取消加载
        const t = timers.get(v);
        if (t) {
          clearTimeout(t);
          timers.delete(v);
        }
        v.pause();
      }
    });
  }, {
    rootMargin: '150px'
  });

  document.querySelectorAll('.lazy-video').forEach(v => io.observe(v));
}

 

防抖+可视范围判断 

 

function videoLazyLoad() {
  const timers = new WeakMap();

  const io = new IntersectionObserver(entries => {
    entries.forEach(e => {
      const v = e.target;

      if (e.isIntersecting && e.intersectionRatio > 0.4) {
        if (timers.has(v)) return;

        const t = setTimeout(() => {
          if (!v.src) v.src = v.dataset.src;
          v.play().catch(() => {});
          timers.delete(v);
        }, 300);

        timers.set(v, t);
      } else {
        const t = timers.get(v);
        if (t) {
          clearTimeout(t);
          timers.delete(v);
        }
        v.pause();
      }
    });
  }, {
    rootMargin: '150px',
    threshold: [0, 0.4, 1]
  });

  document.querySelectorAll('.lazy-video').forEach(v => io.observe(v));
}

 

视频资源请求三次的原因:

一、先给结论(很重要)

这 3 次请求不是你代码的问题,
而是浏览器 video 播放的标准网络行为。

你现在这版代码:

 
if (!v.src) { v.src = v.dataset.src; } v.play();

👉 已经不会再因为 JS 产生额外请求了
👉 再“少”已经做不到


二、这 3 次请求分别是什么(Chrome / Edge 常见)

① 第一次:探测请求(metadata)

 
Range: bytes=0-1 Status: 206 Partial Content

目的

  • 判断视频格式

  • 解析 moov box

  • 获取时长 / 编码信息

📌 只要 video 准备播放,这一步一定发生


② 第二次:开始播放的数据请求

 
Range: bytes=xxxxx-yyyyy Status: 206 Partial Content

目的

  • 拉取可播放的数据段

📌 由 play() 触发


③ 第三次:缓冲 / 预取

 
Range: bytes=yyyyy-zzzzz Status: 206 Partial Content

目的

  • 防止播放卡顿

  • 浏览器内部 buffer 策略

📌 和 JS 无关


三、为什么你“看起来像重复请求了 3 次同一个视频”?

因为你在 Network 面板看到的是:

demo.mp4
demo.mp4
demo.mp4

但实际上是:

demo.mp4 (Range 0-1)
demo.mp4 (Range 123456-234567)
demo.mp4 (Range 234568-345678)

👉 不是重复下载完整文件

 

posted @ 2025-12-24 11:56  SimoonJia  阅读(20)  评论(0)    收藏  举报