使用原生js+canvas实现时实视频截图,图片选择,图片预览等功能,js部分可兼容vue和react。
实现思路:
- canvas 有自己的drawImage 方法 可以传入五个参数 第一个就是video,所以要先获取video的元素。
- 使用canvas drawImage方法创建好了后 在通过createEelement创建一个image,使用canvas.toDataURL生成url。
- 生成出的url是base64格式不能实现在线预览,所以要将base64转为blob,在将blob转换成file对象,有了file对象就可以使用URL.createObjectURL生成可预览的url。
页面元素
<div class="home">
<el-row>
<el-col :span="24">
<!-- 视频 -->
<video class="videoName" controls src="../assets/monster.mp4"></video>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<!-- 截图 -->
<el-button type="primary" round @click="screenshot">点击截图</el-button>
<!-- 选择 -->
<el-button
type="success"
round
@click="clickModel"
:close-on-click-modal="false"
:close-on-press-escape="false"
v-if="selectedList.length !== 0"
>选择完成</el-button
>
</el-col>
</el-row>
<el-row :gutter="10" style="margin-top: 10px">
<!-- 截图 -->
<el-col :span="4" v-for="item in imagesUrl" :key="item" id="row-item">
<div
:class="`demo-image__placeholder item-xxx${item.id}`"
@click="selected(item, `.item-xxx${item.id}`)"
:key="item.id"
>
<div class="block">
<p class="demonstration">{{ item.time }}</p>
<p class="demonstration">{{ item.videoTime }}</p>
<el-image :src="item.url" fit="cover" />
</div>
</div>
</el-col>
</el-row>
<el-dialog
v-model="dialogVisible"
title="图片操作"
width="70%"
:fullscreen="true"
:draggable="true"
:before-close="handleClose"
>
<el-row>
<el-col :span="24">
<p>
{{
`共选择${selectedList.length}张图片 剩余${selectedList.length}张图片未处理`
}}
</p>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<div class="canvas-view">
<canvas
id="myCanvas"
ref="myCanvas"
>
</canvas>
</div>
</el-col>
</el-row>
<el-row style="margin-top: 24px" v-if="selectedList">
<el-col :span="24">
<el-carousel
:autoplay="false"
type="card"
arrow="never"
height="350px"
>
<el-carousel-item
v-for="item in selectedList"
:key="item"
@click="carouselChange(item)"
>
<el-image :src="item.url" fit="cover" />
</el-carousel-item>
</el-carousel>
</el-col>
</el-row>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">关闭</el-button>
<el-button type="primary" @click="dialogVisible = false"
>保存</el-button
>
</span>
</template>
</el-dialog>
</div>
初始化数据
data() {
return {
//截图数据
imagesUrl: [],
//选中数据
selectedList: [],
//弹出窗
dialogVisible: false,
};
},
页面事件
// 截图
screenshot() {
const video = document.querySelector("video");
let canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas
.getContext("2d")
.drawImage(video, 0, 0, canvas.width, canvas.height);
const img = document.createElement("img");
img.src = canvas.toDataURL("image/png");
//转blob
const blob = this.baseToBlob(img.src);
//file 对象 方便提交数据
const files = new File(
[blob],
`${new Date().Format("yyyy-MM-dd HH:mm:ss")} ${(
video.currentTime / 100
).toFixed(2)}/${(video.duration / 60 - 0.03).toFixed(2)}`,
{
type: "image/png",
lastModified: Date.now(),
}
);
//需要在页面渲染的截图数据数组
this.imagesUrl.push({
id: this.imagesUrl.length + 1,
time: `截图时间:${new Date().Format("yyyy-MM-dd HH:mm:ss")} `,
videoTime: `截图视频时间:${(video.currentTime / 100).toFixed(2)}/${(
video.duration / 60 -
0.03
).toFixed(2)}`,
url: URL.createObjectURL(files),
file: files,
});
},
//选中
selected(params, docs) {
console.log(docs);
const doc = document.querySelector(docs);
const isIndex = this.selectedList.findIndex(
(item) => item.id === params.id
);
if (isIndex === -1) {
doc.style.borderStyle = "solid";
doc.style.borderColor = "#42b983";
this.selectedList.push(params);
} else {
doc.style.borderStyle = "none";
this.selectedList.splice(isIndex, 1);
}
},
clickModel(url) {
if (!this.dialogVisible) {
this.dialogVisible = true;
this.changeURL(url);
} else {
this.changeURL(url);
}
},
//排除未选择的数据保留在页面上
handleClose() {
this.dialogVisible = false;
const arr = this.imagesUrl.concat(this.selectedList);
this.selectedList = [];
this.imagesUrl = arr.filter((item) => {
return arr.indexOf(item) === arr.lastIndexOf(item);
});
},
changeURL(url) {
this.$nextTick(() => {
const canvasEvent = this.$refs.myCanvas;
const imageObj = new Image();
if (typeof url !== "object") {
imageObj.src = url;
} else {
imageObj.src = this.selectedList[0].url;
}
canvasEvent
.getContext("2d")
.drawImage(imageObj, 0, 0, canvasEvent.width, canvasEvent.height);
});
},
carouselChange(val) {
this.clickModel(val.url);
},
//转blob
baseToBlob(data) {
let arr = data.split(","),
fileType = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
l = bstr.length,
u8Arr = new Uint8Array(l);
while (l--) {
u8Arr[l] = bstr.charCodeAt(l);
}
return new Blob([u8Arr], {
type: fileType,
});
},
css
.videoName {
height: 500px;
}
#myCanvas {
width: 800px;
height: 400px;
}
.demo-image__placeholder {
padding: 5px;
cursor: pointer;
}