前端实现B站视频画中画功能 - 完整代码实现主页面和小窗同步视频控制功能

前端实现B站视频画中画功能 - 完整代码实现主页面和小窗同步视频控制功能
在这里插入图片描述
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
🎐 个人CSND主页——Micro麦可乐的博客
🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

前端实现B站视频画中画功能 - 完整代码实现主页面和小窗同步视频控制功能

1. 前言

不知道小伙伴是否发现B站的视频播放中,有一个功能 画中画 ,当用户点击会展现一个小窗播放,即使将主窗口缩起来,小窗口依然保留在外面电脑桌面上,如下图 :

开启画中画(小窗口)

在这里插入图片描述


收缩主窗口

在这里插入图片描述

随着 Chrome 116+ 支持 Document Picture‑in‑Picture API 的出现,我们终于可以把整个页面内容(不仅仅是 )移入画中画小窗口中,并在小窗中实现自定义控件、播放进度操作等功能。

本文博主将带着小伙伴们实现一个主页面和小窗同步视频控制功能,例如在主窗口暂停、小窗也同步暂停;调节音量、跳转进度也保持一致,提升用户体验。


2. 为什么要使用 Document PiP API

在当今多任务处理的时代,用户经常需要在观看视频的同时进行其他操作(如浏览信息、回复消息等)。小窗模式(画中画) 解决了这一需求,让视频可以浮动在页面上方,同时用户可以自由浏览其他内容。

与传统 Picture-in-Picture 的区别

在这里插入图片描述
  • 传统 的画中画 API 功能有限,无法带自定义控件与交互
  • 新的 API 可以让整个文档出现在独立的小窗口中,支持丰富交互,如播放、暂停、音量、进度条等
  • 特别适用于视频会议、在线课程、弹幕播放器、以及需要自定义控制画中画的小应用
API 优势
完整HTML支持:可包含按钮、进度条等交互元素
无缝集成:与原始页面共享JavaScript上下文
尺寸灵活:可自定义小窗尺寸
双向通信:主页面与小窗实时同步

3. 完整代码案例

代码中 1.mp4 为博主本地保存测试的视频,大家可以自行获取相应资源源,修改 videosrc 即可

3.1 可直接复用运行代码

CSS代码

*{margin: 0;padding: 0;box-sizing: border-box;}body{font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;line-height: 1.6;color: #333;background:linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);padding: 20px;min-height: 100vh;}.container{max-width: 1200px;margin: 0 auto;background-color:rgba(255, 255, 255, 0.95);border-radius: 15px;box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);overflow: hidden;}header{background:linear-gradient(to right, #1a2a6c, #b21f1f);color: white;padding: 25px 40px;text-align: center;}h1{font-size: 2.5rem;margin-bottom: 10px;text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);}.subtitle{font-size: 1.2rem;opacity: 0.9;max-width: 700px;margin: 0 auto;}.content{display: flex;padding: 30px;gap: 30px;}.video-section{flex: 3;background: #f8f9fa;border-radius: 10px;overflow: hidden;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);}.video-container{position: relative;padding-top: 56.25%;/* 16:9 Aspect Ratio */background: #000;}video{position: absolute;top: 0;left: 0;width: 100%;height: 100%;display: block;}.video-controls{display: flex;padding: 15px;gap: 10px;background: #e9ecef;}button{background: #1a2a6c;color: white;border: none;padding: 10px 20px;border-radius: 5px;cursor: pointer;font-weight: 600;transition: all 0.3s ease;display: flex;align-items: center;gap: 8px;}button:hover{background: #0d1a4d;transform:translateY(-2px);box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);}button:disabled{background: #6c757d;cursor: not-allowed;transform: none;box-shadow: none;}.info-section{flex: 2;background: white;padding: 25px;border-radius: 10px;box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);}h2{color: #1a2a6c;margin-bottom: 20px;padding-bottom: 10px;border-bottom: 2px solid #e9ecef;}.feature-list{margin: 20px 0;}.feature{display: flex;align-items: flex-start;margin-bottom: 15px;}.feature-icon{background: #1a2a6c;color: white;width: 30px;height: 30px;border-radius: 50%;display: flex;align-items: center;justify-content: center;margin-right: 15px;flex-shrink: 0;}.pip-window{position: fixed;bottom: 20px;right: 20px;width: 300px;height: 200px;background: black;border-radius: 10px;overflow: hidden;box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);z-index: 1000;display: none;}.pip-window video{width: 100%;height: 100%;object-fit: cover;}.pip-controls{position: absolute;bottom: 10px;left: 0;right: 0;display: flex;justify-content: center;gap: 10px;opacity: 0;transition: opacity 0.3s;}.pip-window:hover .pip-controls{opacity: 1;}.status{padding: 15px;background: #e9ecef;border-radius: 8px;margin-top: 20px;font-family: monospace;}.browser-support{margin-top: 30px;padding: 20px;background: #fff8e1;border-radius: 8px;border-left: 4px solid #ffc107;}.support-list{display: flex;gap: 15px;margin-top: 15px;flex-wrap: wrap;}.browser{display: flex;align-items: center;gap: 8px;}.supported{color: #28a745;}.unsupported{color: #dc3545;}@media(max-width: 900px){.content{flex-direction: column;}}

HTML代码

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>视频小窗模式演示</title><linkhref="css/pip.css"rel="stylesheet"type="text/css"/></head><body><divclass="container"><header><h1>视频小窗模式演示</h1><pclass="subtitle">使用 Document Picture-in-Picture API 实现在其他内容上浮动播放视频</p></header><divclass="content"><divclass="video-section"><divclass="video-container"><videoid="mainVideo"src="1.mp4"controlsplaysinline></video></div><divclass="video-controls"><buttonid="pipButton"title="开启小窗模式"><svgwidth="20"height="20"fill="currentColor"viewBox="0 0 16 16"><pathd="M0 3.5A1.5 1.5 0 0 1 1.5 2h13A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5v-9zM1.5 3a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h13a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-13z"/><pathd="M8 8.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v3a.5.5 0 0 1-.5.5h-5a.5.5 0 0 1-.5-.5v-3z"/></svg> 开启小窗模式 </button><buttonid="fullscreenButton"><svgwidth="20"height="20"fill="currentColor"viewBox="0 0 16 16"><pathd="M1.5 1a.5.5 0 0 0-.5.5v4a.5.5 0 0 1-1 0v-4A1.5 1.5 0 0 1 1.5 0h4a.5.5 0 0 1 0 1h-4zM10 .5a.5.5 0 0 1 .5-.5h4A1.5 1.5 0 0 1 16 1.5v4a.5.5 0 0 1-1 0v-4a.5.5 0 0 0-.5-.5h-4a.5.5 0 0 1-.5-.5zM.5 10a.5.5 0 0 1 .5.5v4a.5.5 0 0 0 .5.5h4a.5.5 0 0 1 0 1h-4A1.5 1.5 0 0 1 0 14.5v-4a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v4a1.5 1.5 0 0 1-1.5 1.5h-4a.5.5 0 0 1 0-1h4a.5.5 0 0 0 .5-.5v-4a.5.5 0 0 1 .5-.5z"/></svg> 全屏 </button></div></div><divclass="info-section"><h2>Document Picture-in-Picture API</h2><divclass="feature-list"><divclass="feature"><divclass="feature-icon">1</div><div><h3>任意HTML内容</h3><p>可以在小窗中显示视频控件、字幕等任意HTML元素</p></div></div><divclass="feature"><divclass="feature-icon">2</div><div><h3>保持播放状态</h3><p>进入小窗模式时视频持续播放,不中断观看体验</p></div></div><divclass="feature"><divclass="feature-icon">3</div><div><h3>双向同步</h3><p>主页面和小窗中的视频状态实时同步</p></div></div><divclass="feature"><divclass="feature-icon">4</div><div><h3>自由调整</h3><p>用户可以调整小窗位置和大小,适应不同需求</p></div></div></div><divclass="status"><p>当前状态: <spanid="statusText">等待操作</span></p><p>小窗状态: <spanid="pipStatus">未激活</span></p></div><divclass="browser-support"><h3>浏览器支持情况</h3><divclass="support-list"><divclass="browser"><svgwidth="24"height="24"fill="#4285F4"viewBox="0 0 24 24"><pathd="M12 15.6l-3.9 2.3 1-4.3-3.2-2.9 4.3-.4L12 6.5l1.8 4.1 4.3.4-3.2 2.9 1 4.3z"/></svg><spanclass="supported">Chrome 108+</span></div><divclass="browser"><svgwidth="24"height="24"fill="#FF9500"viewBox="0 0 24 24"><pathd="M0 0h24v24H0V0z"fill="none"/><pathd="M17.2 3H6.8l-5.2 9 5.2 9h10.4l5.2-9-5.2-9zm-1.15 16h-8.1l-4.04-7 4.04-7h8.09l4.04 7-4.03 7z"/></svg><spanclass="unsupported">Firefox</span></div><divclass="browser"><svgwidth="24"height="24"fill="#0078D7"viewBox="0 0 24 24"><pathd="M0 0v24h24V0H0zm22 22H2V2h20v20z"/><pathd="M12 12l-4 4 1.4 1.4 2.6-2.6 2.6 2.6 1.4-1.4z"/></svg><spanclass="unsupported">Edge</span></div><divclass="browser"><svgwidth="24"height="24"fill="#000000"viewBox="0 0 24 24"><pathd="M18.7 4.3c-1.2-1.2-2.9-1.9-4.7-1.9H5C3.3 2.4 2 3.7 2 5.4v13.1c0 1.8 1.5 3.2 3.3 3.2H19c1.8 0 3.2-1.4 3.2-3.2V9c0-1.8-.7-3.5-1.9-4.7h-.6zM19 20.5H5.3c-.9 0-1.7-.7-1.7-1.7V5.4c0-.9.8-1.7 1.7-1.7h9c.9 0 1.7.7 1.7 1.7v3.9h3.9c.9 0 1.7.8 1.7 1.7v7.9c0 .9-.7 1.7-1.6 1.7z"/></svg><spanclass="unsupported">Safari</span></div></div></div></div></div></div><!-- 小窗模式容器 --><divid="pipContainer"class="pip-window"><videoid="pipVideo"src="1.mp4"controls></video><divclass="pip-controls"><buttonid="closePipButton"title="关闭小窗">关闭</button></div></div><script>// 页面元素const mainVideo = document.getElementById('mainVideo');const pipVideo = document.getElementById('pipVideo');const pipButton = document.getElementById('pipButton');const fullscreenButton = document.getElementById('fullscreenButton');const closePipButton = document.getElementById('closePipButton');const pipContainer = document.getElementById('pipContainer');const statusText = document.getElementById('statusText');const pipStatus = document.getElementById('pipStatus');// 检查浏览器支持情况const isPipSupported ='documentPictureInPicture'in window;// 初始化页面functioninit(){updateStatus(isPipSupported ?"Document Picture-in-Picture API 可用":"您的浏览器不支持 Document Picture-in-Picture API");// 设置按钮状态 pipButton.disabled =!isPipSupported;// 添加事件监听器 pipButton.addEventListener('click', togglePictureInPicture); fullscreenButton.addEventListener('click', toggleFullscreen); closePipButton.addEventListener('click', closePictureInPicture);// 初始化视频源 pipVideo.src = mainVideo.src; mainVideo.muted =true; pipVideo.muted =true;}// 更新状态显示functionupdateStatus(message){ statusText.textContent = message;}// 更新PIP状态显示functionupdatePipStatus(message){ pipStatus.textContent = message;}// 切换小窗模式asyncfunctiontogglePictureInPicture(){if(!isPipSupported)return;// 如果小窗已打开,则关闭if(window.documentPictureInPicture.window){awaitclosePictureInPicture();return;}try{// 打开小窗const pipWindow =await window.documentPictureInPicture.requestWindow({ width:400, height:300,});// 设置小窗标题 pipWindow.document.title ="视频小窗播放";// 添加样式const style = document.createElement('style'); style.textContent =` body { margin: 0; background: black; height: 100vh; overflow: hidden; } video { width: 100%; height: 100%; object-fit: contain; } `; pipWindow.document.head.appendChild(style);// 添加视频元素到小窗 pipWindow.document.body.appendChild(pipVideo);// 同步播放状态 pipVideo.currentTime = mainVideo.currentTime;if(!mainVideo.paused){await pipVideo.play();}else{ pipVideo.pause();}// 处理小窗关闭事件 pipWindow.addEventListener('pagehide',()=>{// 将视频元素移回主文档 pipContainer.appendChild(pipVideo); pipContainer.style.display ='none';updatePipStatus("已关闭");});// 显示小窗容器(用于样式) pipContainer.style.display ='block';updateStatus("小窗模式已激活");updatePipStatus("运行中");// 同步播放状态 mainVideo.addEventListener('timeupdate', syncVideoTime); pipVideo.addEventListener('timeupdate', syncVideoTime);// 同步播放/暂停状态 mainVideo.addEventListener('play',()=> pipVideo.play()); mainVideo.addEventListener('pause',()=> pipVideo.pause()); pipVideo.addEventListener('play',()=> mainVideo.play()); pipVideo.addEventListener('pause',()=> mainVideo.pause());// 同步音量 mainVideo.addEventListener('volumechange', syncVolume); pipVideo.addEventListener('volumechange', syncVolume);}catch(error){updateStatus(`错误: ${error.message}`); console.error(error);}}// 同步视频播放时间functionsyncVideoTime(){// 避免循环同步if(Math.abs(mainVideo.currentTime - pipVideo.currentTime)>0.5){if(this=== mainVideo){ pipVideo.currentTime = mainVideo.currentTime;}else{ mainVideo.currentTime = pipVideo.currentTime;}}}// 同步音量functionsyncVolume(){if(this=== mainVideo){ pipVideo.volume = mainVideo.volume; pipVideo.muted = mainVideo.muted;}else{ mainVideo.volume = pipVideo.volume; mainVideo.muted = pipVideo.muted;}}// 关闭小窗模式asyncfunctionclosePictureInPicture(){if(window.documentPictureInPicture.window){ window.documentPictureInPicture.window.close();}// 移除事件监听器 mainVideo.removeEventListener('timeupdate', syncVideoTime); pipVideo.removeEventListener('timeupdate', syncVideoTime); mainVideo.removeEventListener('volumechange', syncVolume); pipVideo.removeEventListener('volumechange', syncVolume);// 将视频移回原始位置 pipContainer.appendChild(pipVideo); pipContainer.style.display ='none';updateStatus("小窗模式已关闭");updatePipStatus("未激活");}// 切换全屏模式functiontoggleFullscreen(){if(!document.fullscreenElement){if(mainVideo.requestFullscreen){ mainVideo.requestFullscreen();}elseif(mainVideo.webkitRequestFullscreen){ mainVideo.webkitRequestFullscreen();}elseif(mainVideo.msRequestFullscreen){ mainVideo.msRequestFullscreen();}}else{if(document.exitFullscreen){ document.exitFullscreen();}elseif(document.webkitExitFullscreen){ document.webkitExitFullscreen();}elseif(document.msExitFullscreen){ document.msExitFullscreen();}}}// 初始化应用 document.addEventListener('DOMContentLoaded', init);</script></body></html>

3.2 演示效果

小伙伴们可以根据以下GIF演示图,查看效果

在这里插入图片描述

4. 案例核心原理与流程

4.1 代码流程

  • 用户点击按钮 → 调用 documentPictureInPicture.requestWindow({ width, height }) 创建 PiP 窗口
  • 将包含 <video>DOM 节点移动到小窗口中
  • 在两个窗口里同步播放、暂停、音量、当前时间等状态
  • 窗口关闭时,通过监听 pagehide 或按钮将 DOM 恢复到主窗口

4.2 状态同步机制

在这里插入图片描述

4.3 关键事件处理

  • timeupdate:同步播放进度
  • play/pause:同步播放状态
  • volumechange:同步音量设置
  • pagehide:检测小窗关闭

5. 结语

Document Picture-in-Picture API 为开发者提供了强大的工具来创建更灵活的视频观看体验。虽然目前浏览器支持有限 ( Chrome 116+ ),但随着标准的发展,相信它将成为视频播放页面的标配功能。

本文演示了如何使用 window.documentPictureInPicture.requestWindow() API 创建一个自定义画中画窗口,并实现主窗口与小窗之间同步播放、暂停、音量控制与关闭逻辑。该方案相比传统 PiP 能实现更强的可定制化,适用于自定义播放器场景。

如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家一键三连给博主一点点鼓励!


前端技术专栏回顾:

01【前端技术】 ES6 介绍及常用语法说明
02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解
03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决
05 前端AJAX请求上传下载进度监控指南详解与完整代码示例
06 TypeScript 进阶指南 - 使用泛型与keyof约束参数
07 前端实现视频文件动画帧图片提取全攻略 - 附完整代码样例
08 前端函数防抖(Debounce)完整讲解 - 从原理、应用到完整实现
09 JavaScript异步编程 Async/Await 使用详解:从原理到最佳实践
10 前端图片裁剪上传全流程详解:从预览到上传的完整流程
11 前端大文件分片上传详解 - Spring Boot 后端接口实现
12 前端实现图片防盗链技术详解 - 原理分析与SpringBoot解决方案
13 前端拖拽排序实现详解:从原理到实践 - 附完整代码
14 前端Base64格式文件上传详解:原理、实现与最佳实践
15 一文看懂Proxy与Object.defineProperty深度解析 - JavaScript的拦截艺术

Read more

AI评估建议可信度:破解决策迷局

AI评估建议可信度:破解决策迷局

demo:更新决策数学模型的版本https://www.coze.cn/s/yCV7zGc-F6A/ #人的一生处处在决策,决策的好坏决定结果有没有遗憾,有的人寻求外在建议综合决策,而无法判断建议是否可靠,因此,提出Cognitive Trustworthiness Evaluator, CTE,这是一个极具潜力且前沿的交叉领域项目——将认知科学、行为经济学、概率推理与人工智能结合,构建一个基于认知偏差建模的建议可信度评估智能体(Cognitive Trustworthiness Evaluator, CTE) 一、项目目标 构建一个智能体(Agent),通过分析用户在表达观点、提出建议时所体现出的认知特征(尤其是与概率感、事后归因、幸存者偏差、反事实思维等相关的模式),对其认知可靠性进行量化评分,并据此判断其建议是否值得采纳。 核心假设:一个人对不确定性的理解能力(即“概率感”)及其对因果关系的误判倾向,是其建议质量的重要预测指标。 二、理论基础与关键维度 我们聚焦以下五个核心认知维度,每个维度均有心理学/行为经济学实证支持: 表格 维度定义行为表现可观测信

【AI编程】Qoder AI 编程工具从部署到深度使用实战详解

【AI编程】Qoder AI 编程工具从部署到深度使用实战详解

目录 一、前言 二、AI编程工具介绍 2.1 什么是AI编程 2.1 AI编程核心功能 2.3 AI编程应用场景 1. 智能代码补全与生成 2. 自然语言生成代码 3. 代码解释与文档生成 4. 错误检测与自动修复 5. 单元测试与自动化测试生成 6. 代码重构与优化 7. 跨语言代码转换 8. 低代码/无代码平台增强 三、几种主流AI编程工具介绍 3.1 Cursor 3.1.1 Cursor 核心功能 3.1.1 Cursor 优势 3.2 GitHub Copilot

2026年03月14日全球AI前沿动态

2026年03月14日全球AI前沿动态

一句话总结 2026年3月13日前后,全球科技企业在AI大模型、智能体、硬件基础设施、跨行业应用等领域密集发布新品与技术突破,涵盖模型优化、智能体部署、硬件升级、落地场景拓展等多维度,同步伴随投资并购、政策监管、人才流动及伦理安全争议等行业动态。 一、模型与技术突破 1.1 通用大模型(大语言模型与多模态模型) * 英伟达:发布开源模型Nemotron 3 Super,120B参数,混合Mamba-Transformer架构,原生支持100万token上下文,PinchBench得分85.6%(开源榜首);采用NVFP4格式预训练,适配Blackwell架构,B200芯片推理速度达H100的4倍,吞吐量超上代5倍。 * xAI:发布Grok4.20,非幻觉率78%(创行业纪录),智能指数48分(较前代+6分),每百万令牌成本2-6美元;支持事实可靠推理,适用于严谨行业场景。 * 谷歌:发布Gemini Embedding 2,首个原生多模态嵌入模型,可将文本、

OpenClaw ACP 协议深度解析:让 IDE 直接驱动你的 AI Agent

OpenClaw ACP 协议深度解析:让 IDE 直接驱动你的 AI Agent

OpenClaw ACP 协议深度解析:让 IDE 直接驱动你的 AI Agent 🔗 ACP(Agent Client Protocol)是 OpenClaw 最新的核心基础设施升级 —— 一个连接 IDE 和 OpenClaw Gateway 的通信隧道,让你在 VS Code / Zed 中直接驱动 AI Agent,一切都无需离开编辑器 📑 文章目录 1. 为什么需要 ACP:在 IDE 和 Agent 之间反复横跳的痛苦 2. ACP 30 秒速懂:AI 世界的 Language Server Protocol 3. ACP 架构全景: