ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

全屏视频背景HTML实战:video属性、移动端兼容与性能优化

全屏视频背景HTML实战:video属性、移动端兼容与性能优化 简介一套可直接运行的前端示例面向需要快速实现全屏视频背景的网页前端开发者利用HTML5 video标签与CSS样式解决视频铺满屏幕、保持原始比例不变形、移动端适配等常见问题。压缩包共3个文件包含1个mp4背景视频素材、1个html页面和1个css样式表整体压缩后仅4.11MB轻量易用。目前已有5375人学习过该资源说明这套方案经过实际检验。对照源码可弄清autoplay、loop、muted三个属性的作用理解position:fixed与min-width/min-height如何实现全屏自适应覆盖并掌握object-fit:cover裁切多余画面、避免拉伸变形的方法媒体查询部分给出了小屏设备的自适应调整思路能直接套用到真实项目中。资源还附带一段视频素材替换即可测试不同效果同时代码结构清晰简洁易修改适合前端入门与快速搭建演示页面。1. 全屏视频背景的HTML为什么一个video标签就够撑起整个首屏公司做六周年庆落地页运营第一句话就是“打开页面要有大片感”。GIF试过2MB的图又糊又卡轮播图试过那叫广告牌不叫氛围。最后定了最直接也最稳的方案用HTML做全屏视频背景页面——一个标准!doctype html网页里面放一个video标签配几行CSS把视频钉在整个视口后面文字按钮浮在最上层。这篇文章把这条落地路径完整走一遍HTML骨架、CSS定位、移动端自动播放限制、性能降级以及按屏幕方向切换视频源。适合做活动页、产品首页和个人作品集的人新手能照着参数一步步搭出来熟手可以直接把后文的排查清单拿走用。2. 先把 HTML 骨架搭对video 标签的 5 个关键属性一次到位2.1 全屏视频背景的标准 HTML 写法!doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title全屏视频背景落地页/title link relstylesheet hrefstyle.css /head body !-- 背景视频层 -- div classvideo-bg video classvideo-bg__player autoplay muted loop playsinline postercover.jpg source srcbg.mp4 typevideo/mp4 source srcbg.webm typevideo/webm 你的浏览器不支持 HTML5 视频请更换现代浏览器。 /video /div !-- 前景内容层 -- div classcontent h1全屏视频背景/h1 p文字悬浮在视频之上/p a classbtn href#进入活动/a /div /body /html逻辑说明这个结构把“背景video”和“前景content”分成两个独立区块。.video-bg负责定位和尺寸.video-bg__player是真正的播放器里面的source标签按顺序提供多个格式。当浏览器完全不支持video时会显示标签内部那段提示文字这段文字今天更多是给爬虫和合规检查看的真实用户基本看不到。参数说明autoplay页面加载后立即开播。muted静音同时是现代浏览器自动播放策略的硬性前提光有autoplay没有muted在 Chrome 里会被拦下来。loop循环播放背景视频必须带不然播一遍就黑屏。playsinlineiOS Safari 专用阻止视频自动进入全屏移动端必须带。postercover.jpg视频首帧没准备好之前显示的封面图防止首屏黑块。2.2 五个属性里最容易翻车的组合问题把autoplay和muted放一起说浏览器的自动播放策略桌面端与移动端不完全一样。桌面 Chrome 允许加muted后自动播放如果视频必须带声音就得等用户点击一次才能播这在背景场景里几乎不可接受。所以背景视频的铁律是“永不发声”把muted当成autoplay的前置条件而不是可选项。loop也有隐蔽行为它只负责重复播这个文件不会主动帮你裁段。如果视频素材本身是 2 分钟想在 10 秒处循环必须在剪辑阶段处理在前端用loop是做不到的。更稳妥的做法是压制时就输出一个 8~15 秒的短视频把循环的视觉断点裁干净而不是指望前端代码。playsinline的事故现场少了它iPhone 上视频会自动展开成全屏播放页面里只剩下一个黑框。很多新手在安卓上测试一切正常一到 iPhone 就“黑屏”全是这个属性缺位造成的。还有一个类似角色是disablePictureInPicture背景视频被用户触发后画中画按钮会浮出来视觉上很出戏加一句disablepictureinpicture能消掉但属于非必选优化项。preload同样影响体验默认值是metadata对背景视频来说浏览器要先拿时长、码率这些信息再播放首帧反而变慢追求秒开就把preload设成auto代价是页面加载阶段视频会抢占带宽。我的习惯是桌面页面用auto移动端用metadata配poster后面第 6 章再展开讲这套平衡。2.3 多 source 备源mp4 之外还要准备什么格式一个video里可以放多个source浏览器按顺序找第一个能播的格式。做全屏视频背景我一般准备 mp4 和 webm 两个版本。mp4 用 H.264 编码覆盖面最广webm 用 VP9体积小画质好但 Safari 对 VP9 的支持到 2020 年后才逐步跟上所以顺序上把 mp4 放在前面当默认。这里有个参数细节容易被忽略source标签的type要与服务器返回的 Content-Type 一致否则浏览器可能直接跳过。用 Nginx 时.mp4映射到video/mp4.webm映射到video/webm这两个 MIME 如果被当成application/octet-stream视频标签会集体罢工。压制 mp4 时我会用下面这条 ffmpeg 命令专门为网页播放优化# 转码为网页播放优化的 mp4 背景视频 ffmpeg -i input.mp4 -c:v libx264 -profile:v high -pix_fmt yuv420p -crf 23 -movflags faststart -an bg.mp4逻辑说明这条命令把任意输入转成浏览器兼容性最好的 H.264 编码并在压制时去掉音轨。背景视频不需要声音去音轨能让文件体积明显下降。参数说明-profile:v highH.264 的 High Profile解码兼容性好的常规选择。-pix_fmt yuv420p像素格式强制转成 4:2:0。不转的话很多素材压出来是 422Safari 直接拒绝播放这是视频背景最常见的黑匣子之一。-crf 23质量档位23 是网页视频的常规值背景视频可以放宽到 25 进一步缩小体积。-movflags faststart把 moov 元数据移到文件头部浏览器下载几个字节就能拿到时长信息开始播放对首屏秒播至关重要。如果想压 webm 版本常见做法是把编码器换成libvpx-vp9 -crf 31 -b:v 0。不过 webm 更多是锦上添花mp4 通了大多数场景就够用。3. 用 CSS 把视频钉死在背景下position 与 object-fit 的配合3.1 position: fixed 与 100vw/100vh 的定位方案/* 背景视频容器固定在整个视口后面 */ .video-bg { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 0; overflow: hidden; } /* 视频播放器填满容器并等比裁切 */ .video-bg__player { width: 100%; height: 100%; object-fit: cover; } /* 前景内容浮在视频之上 */ .content { position: relative; z-index: 1; }逻辑说明固定定位让背景不随页面滚动这是“背景”两个字的核心含义。100vw和100vh分别等于视口宽高省去用top/right/bottom/left四值凑尺寸的麻烦。z-index这里我把背景层设为0、内容层设为1避开负 z-index 在层叠上下文里的各种坑。overflow: hidden是防止视频画面超出边界后拽出横向滚动条很多页面做完上下能滚、左右也能滚就是漏了这一条。参数说明z-index: 0和1是在同一个父级下的相对层级够用且不容易翻车。如果项目里已经有大量 z-index 数值注意背景层的 z-index 不要插到导航栏和弹层之间它应该永远低于正文内容。100vh 在 iOS Safari 的地址栏收起时会突然变长视觉上背景底部会跳一下常见替代方案是加一行height: -webkit-fill-available兜底但它和100vh同时存在时优先级比较乱我一般只在明确遇到地址栏跳动问题的页面上加。3.2 object-fit: cover不拉伸、不黑边、按需裁切object-fit是给替换元素用的属性video标签上的表现和img一致。默认值是fill会让视频强行铺满容器并改变原始比例画面里的人脸会被拉宽或压扁cover则保持视频原始比例去填满容器超出容器的部分裁掉既不变形也不留黑边。如果视频主体不在画面中心比如人物站在画面左侧默认裁切会把人削掉。这时用object-position挪裁切重心.video-bg__player { object-fit: cover; object-position: 60% center; }60%的意思是保留画面从左往右 60% 的位置作为裁切中心用比例值比像素值更适合不同分辨率下缩放。对比一下三个取值的实际表现object-fit 取值行为视频背景里的表现fill拉伸填满比例失真人脸变形contain完整显示四周留黑边上下两条黑边cover等比放大并裁掉边缘标准做法cover 是视频背景的默认答案只有一种情况不用它视频本身的比例和容器严格一致比如专门为活动页压制的竖屏视频此时用 fill 也无所谓。3.3 层级与遮罩让文字在视频上依然清晰内容层用position: relative; z-index: 1浮在视频上。但视频画面一亮白字就看不清。两个常用做法一是加半透明遮罩二是给文字加阴影我一般两个一起用。遮罩用伪元素最干净不增加额外 DOM 节点/* 半透明遮罩压暗视频保证前景可读 */ .video-bg::before { content: ; position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0, 0, 0, 0.35); z-index: 1; }逻辑说明.video-bg是 fixed 定位伪元素 absolute 会在它内部铺满rgba(0,0,0,0.35)表示 35% 的黑度。遮罩盖住视频但不影响内容层因为内容层的 z-index 是 1比遮罩的 0 高文字依然在最上层。参数说明黑度 0.3~0.4 是视频背景的常见区间。太深视频氛围全没了太浅白字依然看不清如果视频大面积是亮色直接提到 0.45别犹豫。inset: 0的写法在旧 Edge 里不支持保守写法就是top/right/bottom/left四条面向企业内网页面建议用保守写法。文字阴影是性价比更高的兜底.content h1 { color: #fff; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8); margin: 0; }它不依赖遮罩层级即使某段视频特别亮文字周围那层黑边也能保证可读性。注意text-shadow的模糊半径别太大8px 以上会显得文字四周有脏边。4. 移动端兼容与自动播放限制为什么手机上一片黑4.1 iOS Safari 与 Android Chrome 的自动播放策略差异移动端全屏视频背景最大的翻车场景不是样式而是视频根本不播。iOS Safari 的自动播放策略有声音的视频绝不会自动播放必须用户手势点一次静音视频从 iOS 13 开始允许自动播放但需要playsinline配合不然还是会进全屏。Android Chrome 稍微宽松静音视频可以自动播但如果用户在系统里开了省流量模式视频会被当成大数据包停掉。所以移动端的铁律是autoplay muted loop playsinline一个不能少。我见过太多“桌面端正常、iPhone 黑屏”的案例最后定位都是muted或playsinline没带上。判断逻辑很简单自动播放被允许的前提是“视频被静音且页面内联播放”两个条件缺一个就变黑屏。顺带一提muted属性在 React 这类框架里容易踩坑React 的 diff 算法对muted这种 DOM 属性处理不彻底经常出现属性在 HTML 里写了但运行时没生效的情况。常见做法是在组件挂载后手动把video.muted true硬置一次这也是为什么很多人把代码从静态页面搬到 React 后视频背景突然不播了。4.2 playsinline 属性到底解决了什么playsinline是 iOS Safari 私有属性作用是把视频播放器嵌在页面内部而不是跳转成全屏播放器。对背景视频来说全屏播放等于把用户踢出页面视觉上就是灾难。加上它之后iPhone 上的视频才会老老实实待在.video-bg里循环。它还有一个容易被忽视的连带作用消除自动播放时的黑屏闪烁。某些 iOS 版本上不带playsinline的自动播放会让页面先黑一下再进全屏退出全屏时又黑一下。带playsinline后视频一直内联闪烁问题一起消失。这个属性对 Android 无效但写上去也不会有害所以我的习惯是移动端和桌面端共用一套 HTML不单独写移动端模板。如果担心某些 WebView 不遵守自动播放策略可以在运行时加一道保险用play()返回的 Promise 判断是否真的播起来了// 移动端自动播放失败时把视频换成静态封面 const video document.querySelector(.video-bg__player); video.play().catch(() { video.remove(); document.querySelector(.video-bg).classList.add(video-bg--poster); });逻辑说明play()在浏览器禁止自动播放时会返回一个 rejected Promise这是比监听属性更可靠的运行时判断。catch 里的逻辑把黑屏替换成静态图至少保证页面不难看。注意这段代码要放在 DOM 解析完成之后执行。另外有一个和playsinline经常一起出现的 meta 项meta nameviewport contentwidthdevice-width, initial-scale1, viewport-fitcover。其中viewport-fitcover在 iPhone 的刘海上会让页面延伸到屏幕圆角区域背景视频如果贴着边内容会被圆角挡住一部分。配合 CSS 里的env(safe-area-inset-*)可以做边距避让但那是额外工作如果只是活动页不加viewport-fitcover更省心。提示移动端做全屏视频背景最省心的自检顺序是——先确认视频静音再确认playsinline存在最后模拟 iPhone 看是否黑屏。4.3 移动端首屏黑屏poster 封面与省流量降级自动播放问题解决了移动端还会遇到第二种黑屏视频文件还没下载到能出帧的位置播放器区域就是纯黑。给video加poster属性是最直接的兜底封面图存在时浏览器在视频首帧可用之前会一直显示封面视觉上页面不是黑的而是先看到一张静态图。poster 的选图与压缩技巧不要随便截视频第 0 帧很多视频第 0 帧是黑场。我一般把播放器拖到视频画面最好看的那一帧截图压成 JPG 质量 85宽度控制在 1280px 以内300KB 以下。移动端缩略图没必要用原图反正会被cover裁切用 16:9 横图足够。如果担心移动端流量可以做一层更激进的降级遇到系统的省流量模式直接不加载视频// 用户开启省流量模式时直接移除视频改用背景图 const video document.querySelector(.video-bg__player); if (navigator.connection navigator.connection.saveData) { video.remove(); document.querySelector(.video-bg).classList.add(video-bg--poster); }逻辑说明navigator.connection.saveData在 Chrome 和部分 Android 浏览器里表示用户开启了省流量模式此时移除视频元素会让浏览器停止继续拉流同时通过加类名让容器显示 poster 背景图。这个判断只覆盖部分设备iOS 上没有这个 API所以代码要同时保留 HTML 里的poster属性兜底。参数说明这段脚本要放在video标签之后执行或者包在DOMContentLoaded里放 head 里会因为元素还没解析而拿到 null。省流量模式检测不是百分百可靠当它是加分项而不是依赖项不要因为这段代码而省略poster属性。5. 性能与降级避坑视频背景变卡、黑屏、加载慢的排查手册5.1 视频体积控制时长、码率与首帧时间的取舍视频背景是页面里最重的资源它一动整页的网络与 CPU 都跟着动。体积上限没有绝对标准但有个经验值全屏背景视频压缩后尽量控制在 2MB 以内超过 3MB 就是高危区。时长控制在 8~15 秒循环播放下用户其实不会看完 15 秒重点是画面运动不要有循环跳变。压多少码率取决于分辨率。做视频背景我基本只压两个版本桌面端 1080p移动端 720p。分辨率建议码率典型用途720p1.2~2 Mbps移动端默认源1080p2~4 Mbps桌面端默认源2K/4K8~20 Mbps不推荐做背景除非视频本身是内容决定首帧速度的不只是码率还有 moov 元数据位置。第 2 章 ffmpeg 命令里的faststart就是干这个的。压制时没加的话浏览器要下载完整个文件才能找到播放索引视频会长时间停在黑屏。已经压完的文件可以用下面这条命令把 moov 位置挪回来# 只改封装结构不重新编码 ffmpeg -i old.mp4 -c copy -movflags faststart new.mp4逻辑说明-c copy不做重编码只是把封装结构改掉速度很快不会损失画质。这个操作经常在视频已经上传到 CDN 之后才想起来所以最好在压制流程里就把faststart加进固定命令。参数说明-c copy告诉 ffmpeg 复制音视频流不重新压缩-movflags faststart重新组织文件结构。如果原文件没有音轨这条命令也适用。5.2 降级策略加载失败或卡住时自动换成静态图视频背景最大的风险不是慢是彻底加载不出来。弱网环境下等待视频首帧的每一秒用户都在流失。我习惯给视频元素挂三个事件error、stalled、canplaythrough。// 视频加载失败或卡住时自动降级为静态封面 const video document.querySelector(.video-bg__player); const container document.querySelector(.video-bg); function fallbackToPoster() { container.classList.add(video-bg--poster); video.remove(); } video.addEventListener(error, fallbackToPoster); video.addEventListener(stalled, () { setTimeout(() { if (video.readyState 2) fallbackToPoster(); }, 3000); }); video.addEventListener(canplaythrough, () { container.classList.remove(video-bg--poster); });配套 CSS/* 降级模式容器直接显示封面背景图 */ .video-bg--poster { background-image: url(cover.jpg); background-size: cover; background-position: center; }逻辑说明error覆盖文件 404、编码不支持、网络中断等硬错误stalled表示播放器尝试读数据但一直没读到给它 3 秒缓冲如果readyState还没到 2表示还没有足够数据开始播放就主动放弃视频改用静态图。canplaythrough是数据充足到“预计能播完”的事件满足时移除降级类名保证视频正常播放时背景图不会叠在上面。参数说明readyState是 0 到 4 的数字2 代表HAVE_CURRENT_DATA当前帧数据可用。3 秒是经验值太短会误杀弱网但能播的情况太长用户已经等得不耐烦。这里不做重试因为背景视频本来就不值得为它等那么久。5.3 高频故障现场黑屏、全屏、留黑边、层级吞掉背景的逐一排查现象桌面端和手机端视频都不播放页面一直显示 poster 图。 原因autoplay写了但muted没写Chrome 的自动播放策略拦截了有声音的视频。 解决补muted如果用 React挂载后手动执行video.muted true因为 React 对muted属性的同步有时不生效。现象iPhone 上视频自动变成全屏页面里只剩一个黑块。 原因缺playsinlineiOS 把视频当成希望全屏播放的内容。 解决在 video 标签补上playsinline属性并确认 HTML 是从标准!doctype html开始的。非标准文档模式下 WebKit 对属性的解析会变得诡异黑屏排查半天最后发现是 doctype 丢了的案例不少。现象视频不变形了但四周出现黑边上下和左右都有。 原因object-fit没设或者样式被更高优先级的选择器覆盖。 解决确认.video-bg__player同时设置了width: 100%; height: 100%; object-fit: cover;用 DevTools 检查盒模型确认高度真的撑满了容器。现象背景视频看不见页面直接露出 body 背景色。 原因.video-bg用了z-index: -1同时 body 或其祖先有transform、filter、will-change等属性创建了新的层叠上下文负 z-index 被压到最底层。 解决改用“背景层 z-index: 0、内容层 z-index: 1”的方案不要依赖负 z-index。这属于 CSS 层叠上下文的玄学排查成本极高建议直接把负 z-index 从技术方案里排除。现象首屏先黑 2 秒然后突然出画面。 原因poster 没设置视频首帧之前的播放器区域就是黑块或者 mp4 没有加faststart浏览器需要更久才能定位到视频流。 解决设置 poster 封面图并确保 mp4 加过faststart弱网场景按 5.2 的降级逻辑直接给静态图。6. 进阶根据屏幕方向与网络状态动态切换视频源6.1 用 matchMedia 监听横竖屏切换不同分辨率与构图全屏视频背景在手机上真正难的是构图。竖屏手机放横屏视频cover裁切后只留中间一条主角的脸可能被削到画面外。常见做法是准备横屏和竖屏两个版本部署时用matchMedia监听方向切换。HTML 里放两个 video!-- 横屏版本与竖屏版本常驻JS 控制播放权 -- video classvideo-bg__player video-bg__player--landscape autoplay muted loop playsinline source srclandscape.mp4 typevideo/mp4 /video video classvideo-bg__player video-bg__player--portrait autoplay muted loop playsinline source srcportrait.mp4 typevideo/mp4 /video// 横竖屏切换时让对应方向的视频接管播放 const landscape document.querySelector(.video-bg__player--landscape); const portrait document.querySelector(.video-bg__player--portrait); const mql window.matchMedia((orientation: portrait)); function switchByOrientation(e) { if (e.matches) { portrait.play(); landscape.pause(); } else { landscape.play(); portrait.pause(); } } mql.addEventListener(change, switchByOrientation); switchByOrientation(mql);逻辑说明两个 video 都常驻 DOM但同一时间只让一个真正在播另一个用pause停住。切换时播放器从暂停状态继续循环视频正好可以接着当前进度走闪烁感不强。如果两个视频内容完全同步可以进一步监听timeupdate让它们对齐但背景视频没人盯着帧看这一步可以省略。参数说明matchMedia((orientation: portrait))返回的 MediaQueryList 在横竖屏切换时触发change事件最后手动执行一次switchByOrientation(mql)是为了在页面加载时就决定用哪个视频而不是等第一次旋转才初始化。Safari 14 之前的版本不支持addEventListener老代码里常见的是addListener要做老机型兼容可以在调用前判断一下。6.2 预加载与懒加载的平衡让首屏秒开视频是最占用带宽的资源一个页面如果同时还有几张商品图或者雪碧图视频默认的preloadauto会把它们全部挤到后面。我的习惯是桌面端用preloadauto追求秒开移动端先用preloadmetadata配合 poster等页面整体加载完再让视频开始拉流。实现方式不复杂给 HTML 加一个>!-->// 页面其他关键资源加载完再让视频开始拉流 window.addEventListener(load, () { const video document.querySelector([data-defer]); if (video) { video.preload auto; video.load(); } });逻辑说明window.load表示页面上所有图片、样式、脚本都加载完了此时再让视频抢占带宽不会拖慢首屏关键资源。video.load()会按新的preload策略重新拉取数据播放器从metadata阶段切到auto阶段。如果用户已经在弱网下等了很久还没到load5.2 的降级逻辑会自动顶上。参数说明style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表