常见video / image上传操作-校验、截取首帧和正方形预览图等。
上回搞了一个视频和图片上传和校验的需求,感觉学到很多,一些常见的函数记录如下:
1. 图片校验尺寸
const { maxCount = 30, maxWidth, maxHeight, minHeight = 200, minWidth = 200 } = props;
// 文件个数和内存大小、格式可以提前校验,不用请求图片信息。const reader = new FileReader();
reader.readAsDataURL(file);
// eslint-disable-next-line no-loop-func
reader.onload = (e) => {const img = new Image();img.src = e.target.result;img.onload = () => {const width = img.width;const height = img.height;if ((maxWidth && width > maxWidth) ||(maxHeight && height > maxHeight) ||(minWidth && width < minWidth) ||(minHeight && height < minHeight)) {resolve(false);} else {// 通过全部校验,处理图片resolve(true)}};
};
2. 视频校验尺寸
需要了解一下视频加载中的触发事件顺序。
参考:
https://guste.github.io/2018/07/24/video%E6%A0%87%E7%AD%BE%E4%BA%8B%E4%BB%B6%E6%8C%87%E5%8C%97/
const { maxCount = 30, maxWidth, maxHeight, minHeight = 200, minWidth = 200 } = props;// 文件个数和内存大小、格式可以提前校验,不用请求图片信息。// 视频请求文件信息用createObjectURLlet video = document.createElement('video');video.currentTime = 1; // 取封面首帧需要currentTime=1video.src = window.URL.createObjectURL(file);// 获取视频的元数据,但是文件不一定加载出来,此处可以进行校验video.addEventListener('loadedmetadata', (e) => {if ((maxWidth && video.videoWidth > maxWidth) ||(maxHeight && video.videoHeight > maxHeight) ||(minWidth && video.videoWidth < minWidth) ||(minHeight && video.videoHeight < minHeight) ||(maxDuration && video.duration > maxDuration)) {resolve(false)} else {// 进行下一步 loadeddata 事件,取封面首帧// 此时视频画面信息不一定加载出来,取出来的画面是纯黑色。}});// 校验完毕,处理封面图和展示图video.addEventListener('loadeddata', (e) => {// 为了保险可以在这里再校验一次,不通过的resolve(false)// 取首帧,函数附下。const poster = getVideoPoster(video)});};
3. 视频截取第一帧
参考:https://juejin.cn/post/6844903933631004679
此处注意一个小坑:video视频设置currentTime不起作用?(我们要求视频出现需要跳转开头),但是设置currentTime=0不起作用,后面经过一番搜索,设置成currentTime=‘0’
才能达到效果。要赋值字符串才行。
web前端处理图片首选当然是canvas。
// 注意传入的video是上文中已经赋值了src的DOM节点,并且设置currentTime=1。
// 存在有文章说即使在loadeddata事件中仍然截取为黑色,可以尝试将video属性赋值“muted” "autoplay" “preload”,因为在chrome中只有静音的视频才能自动播放,这样截取出的第一帧就会有值。getVideoPoster = (video) => {const canvas = document.createElement('canvas');canvas.width = video.videoWidth;canvas.height = video.videoHeight;const ctx = canvas.getContext('2d');ctx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);return canvas.toDataURL('image/jpg', 1);};完整截取的上传函数如下:
onChange = (e)=>{const uploadFiles = e.target.files?.[0] || {};let video = document.createElement('video');video.currentTime = '1'; // 取封面首帧需要currentTime=1video.src = window.URL.createObjectURL(file);video.addEventListener('loadeddata', (e) => {const poster = getVideoPoster(video)});
}
4. 截图方形预览图(视频 / 图片通用)
上文中已经截取到了base64格式的视频首帧,因此预览图从封面中截取,此处截取的规则如下:图片自适应在封面(设置为200*200)大小,短的那一条边和200px等宽,全部取用,而更长的那一条边取居中长度裁剪。(这么说不知道能不能形容清楚)
在裁剪之前简单介绍一下canvas裁剪的函数,其实还是靠最基本的drawImage。
ctx.drawImage(绘画对象, x, y, imageSizeX, imageSizeY, canvasX, canvasY, canvasSizeX, canvasSizeY);
使用这个函数的含义:
将绘画对象花在画布上,首先从绘画对象的(x,y)坐标开始,横着沿x轴画一条imageSizeX这么长的线,然后再纵向沿着y轴画一条imageSizeY这么长的线,我们就在【原图】上画了一个长方形。
然后把这个长方形放进canvas里
// 可以是imgDom或者videoDom,Dom节点就是上文中我们创建的节点对象。squareImage = (imgDom) => {const width = imgDom.videoWidth || imgDom.width;const height = imgDom.videoHeight || imgDom.height;const canvasSize = 200;var size = Math.min(width, height);const canvas = document.createElement('canvas');canvas.width = canvasSize;canvas.height = canvasSize;const ctx = canvas.getContext('2d');const offsetX = width >= height ? (width - size) / 2 : 0;const offsetY = width >= height ? 0 : (height - size) / 2;ctx.drawImage(imgDom, offsetX, offsetY, size, size, 0, 0, canvasSize, canvasSize);return canvas.toDataURL('image/jpg', 0.9);};
5. 图片压缩
参考之前的调研文章
compressImage = (img) => {const canvas = document.createElement('canvas');canvas.width = img.width;canvas.height = img.height;const ctx = canvas.getContext('2d');ctx.drawImage(img, 0, 0, img.width, img.height);return canvas.toDataURL('image/jpg', 0.7);};
6. base64转二进制字节流上传
dataURItoFile = (dataURI) => {var byteString = atob(dataURI.split(',')[1]);var ab = new ArrayBuffer(byteString.length);var ia = new Uint8Array(ab);for (let i = 0; i < byteString.length; i++) {ia[i] = byteString.charCodeAt(i);}return new Blob([ab], { type: 'image/png' });};