JS实现截屏和编辑图片(隐藏,高亮,裁剪)
产品要求添加的功能,本来功能是用户上传图片,添加新需求是可以点击button截屏然后编辑(隐藏敏感信息,高亮和裁剪)。参考的是google的截屏. gif没显示的是选择截屏的选项, 已添加截图
google take screenshot select what to capture中间遇到了很多问题,记录下。
1, 截图
canvas
第一个问题就是截图,最开始考虑的是htmlcanvas来截图
https://hackernoon.com/how-to-take-screenshots-in-the-browser-using-javascript-l92k3xq7
https://www.cnblogs.com/chunying/p/17022025.html
https://blog.csdn.net/m0_56976301/article/details/127776427
但是canvas只截取当前网页。需要引入第三方库。上代码
html2canvas(document.body, {
ignoreElements: (el) => {
if (el.classList.contains('upload')) {
if (el.tagName==="DIV") {
return true;
}
}
}
}).then(canvas => {
this.img.nativeElement.setAttribute('src', canvas.toDataURL());
});
MediaDevices.getDisplayMedia()
https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getDisplayMedia
这个是web API,不需要引入库,而且可以选择当前电脑的屏幕,tab等,现代浏览器也基本支持。所以选择了这个。代码
async capture() {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const video: any = document.createElement("video");
const gdmOptions = {
video: true,
preferCurrentTab:true
}
try {
const mediaDevices : any = navigator.mediaDevices;
const captureStream = await mediaDevices.getDisplayMedia(gdmOptions);
video.srcObject = captureStream;
console.dir(video);
console.dir(captureStream);
await video.play();
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
context.drawImage(video, 0, 0, canvas.width, canvas.height);
let size = canvas.toDataURL().length*3/4;
// let s = size > 1048576 ? (size/1048576).toFixed(2) + 'MB' : (size/1024).toFixed(1) + 'KB';
this.editImage = true;
setTimeout(() => {
// console.log(this.can);
this.can.nativeElement.setAttribute('src', canvas.toDataURL());
}, 50);
captureStream.getTracks().forEach(track => track.stop());
} catch (err) {
console.error("Error: " + err);
}
};
编辑
重要的就是编辑的逻辑
肯定要用canvas画图
https://baijiahao.baidu.com/s?id=1733267341500955304&wfr=spider&for=pc
布局
图片层,然后在图片层之上画布层,画图在画布层做,保存在合并
<div style="display: inline-block; position: relative;">
<img id="can" (dragstart)="stopDrag()" #can/>
<canvas style="position: absolute; width: 100%; height: 100%; left: 0; top: 0;" id="canvas" #canvas></canvas>
<div id="drawState" #drawState style="width: 100%; height: 100%; position: absolute;left: 0;top: 0;" (mousedown)="canvasStart($event)" (mousemove)="canvasDraw($event)" (mouseup)="canvasDone($event)" (mouseleave)="canvasDone($event)"></div>
</div>
hide就是画矩形然后stroke黑色,
遇到的问题
画布大小
开始画的时候发现画的路径和鼠标的路径不一样,这里就有个canvas的clientWidth/clientHeight 和width/height
canvas显示的大小和画布实际大小不一样,所以一定要记得设置画布大小,如果两个尺寸不一样的话在画的时候还要进行坐标转换
InitCanvasInstance() {
if (!this.ctx) {
let canvas: any = this.canvas.nativeElement;
let img:any = this.img.nativeElement;
this.canvasRatio.width = can.naturalWidth/canvas.clientWidth;
this.canvasRatio.height = can.naturalHeight/canvas.clientHeight
canvas.width = can.naturalWidth;
canvas.height = can.naturalHeight;
this.ctx = canvas.getContext('2d');
this.strokeColor = "#0072a3";
}
highlight的实现
highlight是要把图片加一个遮罩层然后高亮部分不加。这块逻辑包括遮罩还得是canvas。
canvas画一个和画布大小一样的矩形, stroke图片的遮罩颜色,需要高亮的部分clear一下就实现了,遮罩加个flag,如果有了就不再重复加了
清除path
画图开始记录开始坐标,在画图的过程中怎么清楚上一步的路径也是问题,因为我们在画的过程中要实时显示当前选中的区域,也就是在这个过程中一直在画
想了一个比较好现实也比较切合实际的办法就是在当前画布层之上再加一个画布层,每次mousemove的时候都把这个画布层全部clear,鼠标up的时候把最后这一步的path画到下面画布上,这个画布层clear
start
canvasStart (){
this.drawing = true;
let canvas: any = this.canvas.nativeElement;
this.startCoordinate = {x: e.offsetX, y: e.offsetY};
this.endCoordinate = {x: e.offsetX, y: e.offsetY};
let statusCanvas = document.createElement('canvas');
statusCanvas.width = canvas.width
statusCanvas.height = canvas.width;
this.statusCtx = statusCanvas.getContext('2d');
this.drawState.nativeElement.appendChild(statusCanvas);
}
drawing
canvasDraw(e: MouseEvent) {
let canvas: any = this.canvas.nativeElement;
this.statusCtx.beginPath();
this.statusCtx.clearRect(0, 0, canvas.width, canvas.height);
this.statusCtx.strokeStyle = this.strokeColor;
this.statusCtx.strokeRect(this.startCoordinate.x*this.canvasRatio.width, this.startCoordinate.y*this.canvasRatio.height , (e.offsetX-this.startCoordinate.x)*this.canvasRatio.width, (e.offsetY-this.startCoordinate.y)*this.canvasRatio.height);
this.statusCtx.closePath();
}
done
canvasDone(e: MouseEvent) {
let canvas: any = this.canvas.nativeElement;
this.statusCtx.beginPath();
this.statusCtx.clearRect(0, 0, canvas.width, canvas.height);
this.statusCtx.closePath();
this.ctx.beginPath();
if (this.type === 'hide') {
this.ctx.fillStyle = this.fillColor;
this.ctx.fillRect(this.startCoordinate.x*this.canvasRatio.width, this.startCoordinate.y*this.canvasRatio.height, (e.offsetX-this.startCoordinate.x)*this.canvasRatio.width, (e.offsetY-this.startCoordinate.y)*this.canvasRatio.height);
}
if (this.type === 'highlight') {
this.ctx.strokeStyle = this.strokeColor;
this.ctx.clearRect(this.startCoordinate.x*this.canvasRatio.width, this.startCoordinate.y*this.canvasRatio.height, (e.offsetX-this.startCoordinate.x)*this.canvasRatio.width, (e.offsetY-this.startCoordinate.y)*this.canvasRatio.height);
this.ctx.strokeRect(this.startCoordinate.x*this.canvasRatio.width, this.startCoordinate.y*this.canvasRatio.height , (e.offsetX-this.startCoordinate.x)*this.canvasRatio.width, (e.offsetY-this.startCoordinate.y)*this.canvasRatio.height);
}
this.ctx.closePath();
let statusCanvas: HTMLElement = this.drawState.nativeElement;
for (let i = statusCanvas.childNodes.length - 1; i >= 0; i -- ) {
statusCanvas.removeChild(statusCanvas.childNodes[i]);
}
this.drawing = false;
}
保存
保存的时候在创建一个画布将图片和画布的路径层画到上面然后保存就可以了
saveScreenshot() {
let canvas: any = this.canvas.nativeElement;
let newCanvas = document.createElement('canvas');
let can: any = this.can.nativeElement;
newCanvas.width = can.naturalWidth;
newCanvas.height = can.naturalHeight;
let newCtx = newCanvas.getContext('2d');
var image = new Image();
image.src = can['src'];
newCtx.drawImage(image, 0, 0, newCanvas.width, newCanvas.height);
newCtx.drawImage(canvas, 0, 0, newCanvas.width, newCanvas.height);
this.clear(); // 取消,clear,或者done的时候记得清除画布就可以了
document.getElementById('scr').setAttribute('src', newCanvas.toDataURL())
}
效果