JS實現(xiàn)截屏和編輯圖片(隱藏奥吩,高亮具伍,裁剪)

產(chǎn)品要求添加的功能,本來功能是用戶上傳圖片圈驼,添加新需求是可以點擊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只截取當前網(wǎng)頁。需要引入第三方庫楼誓。上代碼

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等主守,現(xiàn)代瀏覽器也基本支持。所以選擇了這個榄融。代碼

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黑色涎才,

遇到的問題

畫布大小

開始畫的時候發(fā)現(xiàn)畫的路徑和鼠標的路徑不一樣,這里就有個canvas的clientWidth/clientHeight 和width/height

canvas顯示的大小和畫布實際大小不一樣力九,所以一定要記得設(shè)置畫布大小耍铜,如果兩個尺寸不一樣的話在畫的時候還要進行坐標轉(zhuǎn)換

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的實現(xiàn)

highlight是要把圖片加一個遮罩層然后高亮部分不加。這塊邏輯包括遮罩還得是canvas跌前。

canvas畫一個和畫布大小一樣的矩形棕兼, stroke圖片的遮罩顏色,需要高亮的部分clear一下就實現(xiàn)了舒萎,遮罩加個flag程储,如果有了就不再重復加了

清除path

畫圖開始記錄開始坐標,在畫圖的過程中怎么清楚上一步的路徑也是問題臂寝,因為我們在畫的過程中要實時顯示當前選中的區(qū)域章鲤,也就是在這個過程中一直在畫

想了一個比較好現(xiàn)實也比較切合實際的辦法就是在當前畫布層之上再加一個畫布層,每次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;

? ? }

保存

保存的時候在創(chuàng)建一個畫布將圖片和畫布的路徑層畫到上面然后保存就可以了

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())

? ? }

效果


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末皱蹦,一起剝皮案震驚了整個濱河市煤杀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沪哺,老刑警劉巖沈自,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異辜妓,居然都是意外死亡枯途,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門籍滴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酪夷,“玉大人,你說我怎么就攤上這事孽惰⊥砹耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵勋功,是天一觀的道長坦报。 經(jīng)常有香客問我,道長狂鞋,這世上最難降的妖魔是什么燎竖? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮要销,結(jié)果婚禮上构回,老公的妹妹穿的比我還像新娘。我一直安慰自己疏咐,他們只是感情好纤掸,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浑塞,像睡著了一般借跪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酌壕,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天掏愁,我揣著相機與錄音,去河邊找鬼卵牍。 笑死果港,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的糊昙。 我是一名探鬼主播辛掠,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了萝衩?” 一聲冷哼從身側(cè)響起回挽,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猩谊,沒想到半個月后千劈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡牌捷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年队塘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宜鸯。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遮怜,靈堂內(nèi)的尸體忽然破棺而出淋袖,到底是詐尸還是另有隱情,我是刑警寧澤锯梁,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布即碗,位于F島的核電站,受9級特大地震影響陌凳,放射性物質(zhì)發(fā)生泄漏剥懒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一合敦、第九天 我趴在偏房一處隱蔽的房頂上張望初橘。 院中可真熱鬧,春花似錦充岛、人聲如沸保檐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夜只。三九已至,卻和暖如春蒜魄,著一層夾襖步出監(jiān)牢的瞬間扔亥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工谈为, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旅挤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓伞鲫,卻偏偏與公主長得像谦铃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子榔昔,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容