古之立大事者,不惟有超世之才,亦必有坚忍不拔之志
个人CSND主页——Micro麦可乐的博客
《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战
《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战
《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解
《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用
✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧
《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程
《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整
《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~
相信很多小伙伴在一些短视频平台上传视频的时候,系统会自动帮我们生成一些视频中的画面帧的图片,让我们作为视频封面的功能,那么这些短视频平台是如何从视频中抽取关键帧来作为封面、生成缩略图,或用于制作动图预览的?
今天博主就带着大家一起来探讨这个问题,当然实现画面帧的方式前端和后端均可实现,具体要看小伙伴们的应用场景,这里我们先介绍前端的实现方式,后续博主再出一篇 基于JAVA实现的视频画面帧教程~
上面我们提到了获取画面帧用于视频封面,实际上还会有很多的使用场景,比如:
前端实现的视频文件动画帧也有它的优缺点:
- 实现简单,依赖浏览器内置 API,无需额外库,适合常见 MP4/WebM/OGG 等格式
- 浏览器对视频格式支持有限,无法处理非标准格式;
- 精确的帧定位依赖浏览器的
currentTime
跳转和loadeddata
事件,可能产生误差或丢帧;- 对大批量帧抽取性能较差,主线程阻塞风险高
利用 HTML5
的 canvas
元素可以直接对视频进行像素级操作,无需后端处理即可完成简易的帧截图功能。
将 元素加载视频资源,通过 canvas.drawImage(video, …) 将当前帧绘制到画布上,再调用 canvas.toDataURL() 或 canvas.toBlob() 获取图片数据
要实现上述思路,我们需要以下几个步骤:
1、通过
获取视频文件
2、使用URL.createObjectURL
创建视频源
3、监听视频元数据加载完成
4、通过Canvas
绘制当前帧
5、将Canvas
转换为图片数据
下面以 HTML5 Canvas
方案为例,演示一个完整的帧提取与下载流程。
设置了两个功能按钮,一个是自动获取所有帧,一个是获取视频当前播放帧,小伙伴们可以自行根据自己需求进行代码修改~
DOCTYPE html>
<html>
<head>
<title>视频帧提取工具 Demotitle>
<style>
.container {
max-width: 800px;
margin: 20px auto;
}
#preview {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.frame-img {
width: 150px;
border: 1px solid #ccc;
}
#video {
width: 300px;
margin-top: 20px;
}
#all img {
width: 50px;
margin-bottom: 20px;
}
style>
<script src="extractFrames.js">script>
head>
<body>
<h1>视频帧提取工具 Demoh1>
<div class="container">
<input type="file" id="videoInput" accept="video/*">
<button onclick="captureFrame()">截取当前帧button>
<button id="captureAll">获取所有帧button>
<div>
<video id="video" controls muted playsinline>video>
div>
<canvas id="canvas" style="display: none;">canvas>
<div id="all">div>
<div id="preview">div>
div>
body>
html>
前端引入的视频帧处理 extractFrames.js
代码
// extractFrames.js 代码
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// 文件选择处理
document.getElementById('videoInput').addEventListener('change', function(e) {
const file = e.target.files[0];
if (!file) return;
const videoURL = URL.createObjectURL(file);
video.src = videoURL;
video.play().catch(() => video.pause()); // 确保加载 metadata
video.play();
});
// 视频元数据加载
video.addEventListener('loadedmetadata', () => {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
});
// 帧捕获函数
function captureFrame() {
if (!video.src) return;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const dataURL = canvas.toDataURL('image/png');
const img = new Image();
img.src = dataURL;
img.className = 'frame-img';
document.getElementById('preview').appendChild(img);
}
//获取所有帧
// 主流程:读取文件并批量截帧
document.getElementById('captureAll').addEventListener('click', async () => {
const duration = video.duration;
const interval = 1; // 每 1 秒抽一帧
for (let t = 1; t < duration; t += interval) {
const blob = await captureAll(t);
const imgURL = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = imgURL;
img.width = 160;
document.getElementById('all').appendChild(img);
}
URL.revokeObjectURL(url);
});
// 获取指定时间点的帧
function captureAll(time) {
return new Promise((resolve) => {
video.currentTime = time;
video.addEventListener('seeked', function onSeeked() {
ctx.drawImage(video, 0, 0,canvas.width, canvas.height);
canvas.toBlob(blob => {
resolve(blob);
}, 'image/png');
video.removeEventListener('seeked', onSeeked);
});
});
}
文件上传处理
- 通过 获取用户上传的视频文件
- 使用 URL.createObjectURL 创建临时视频源地址
视频初始化
- 监听
loadedmetadata
事件获取视频原始尺寸- 根据视频尺寸初始化 Canvas 画布
当前帧捕获
- 通过 drawImage 将当前视频帧绘制到 Canvas
- 使用 toDataURL 将 Canvas 内容转换为 Base64 图片
- 动态创建 Image 元素展示捕获结果
所有帧帧捕获
- 通过监听 seeked 事件,确保视频跳转已完成后再绘制帧
- 使用
canvas.toBlob()
可以获得原生 Blob 对象- 按秒为单位根据视频时间循环生产帧图片
至此在纯前端环境下基于 HTML5 Canvas API
的视频帧提取方案已经演示完毕了,在处理一些格式简单、帧数较少的场景,直接使用 Canvas
即可快速实现,但需要更多格式或大规模自动化截帧,推荐引入 FFmpeg Wasm
库(如 ffmpeg.wasm
)进行处理。
通过本文的讲解,相信小伙伴们已经快速掌握前端视频帧提取技巧,如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!
前端技术专栏回顾:
01【前端技术】 ES6 介绍及常用语法说明
02【前端技术】标签页通讯localStorage、BroadcastChannel、SharedWorker的技术详解
03 前端请求乱序问题分析与AbortController、async/await、Promise.all等解决方案
04 前端开发中深拷贝的循环引用问题:从问题复现到完美解决
05 前端AJAX请求上传下载进度监控指南详解与完整代码示例
06 TypeScript 进阶指南 - 使用泛型与keyof约束参数