上回搞了一个视频和图片上传和校验的需求,感觉学到很多,一些常见的函数记录如下:
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)
}
};
};
需要了解一下视频加载中的触发事件顺序。
参考:
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;
// 文件个数和内存大小、格式可以提前校验,不用请求图片信息。
// 视频请求文件信息用createObjectURL
let video = document.createElement('video');
video.currentTime = 1; // 取封面首帧需要currentTime=1
video.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)
});
};
参考: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=1
video.src = window.URL.createObjectURL(file);
video.addEventListener('loadeddata', (e) => {
const poster = getVideoPoster(video)
});
}
上文中已经截取到了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);
};
参考之前的调研文章
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);
};
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' });
};