从canvas截图并裁剪 & 使用遮罩绘制裁剪选框

@Pelom  April 5, 2022

前言

尽管可以通过操作系统自带的截图工具进行截图(Win11下快捷键Win+Shift+S),在前端截取图像的分辨率还是存在不同(基于HTMLCanvasElement.widthHTMLCanvasElement.height),且可以进行额外处理(如去除背景)

流程

从\<canvas\>截图到\<img\>

在对图像进行裁剪之前,需要先从不断变化的<canvas>中截取静止的一帧,这里使用HTMLCanvasElement.toBlob()方法,它相比另一个方法HTMLCanvasElement.toDataURL()性能更好

canvas.toBlob(function (blob) {
    var url = URL.createObjectURL(blob);
    $('#screenshot>img').attr('src', url);
});

其中url即为截图的链接

  • 如果在使用webgl绘图时截取的图片为全黑,请在获取<canvas>webgl上下文时传入参数preserveDrawingBuffer: true,意为保留缓冲区

    canvas = document.getElementById('canvas');
    gl = canvas.getContext('webgl', { preserveDrawingBuffer: true })

在\<img\>上进行裁剪

裁剪的动作为拖动光标画出一个矩形框,并以框选区与未选区的明暗差异作为实时反馈

拖动鼠标

在按下左键后再绑定拖动鼠标事件,在松开左键时移除该事件,并添加右键的退出事件
通过设置元素的CSS变量来传递参数,在CSS中通过函数var()获取

$('#screenshot').on('mousedown', function (e) {
    if (e.which == 1) {
        e.preventDefault();
        var sx, sy, ex, ey;
        sx = e.clientX, sy = e.clientY;
        $(this).css({
            '--sx': sx + 'px',
            '--sy': sy + 'px'
        }).on('mousemove', function (e) {
            ex = e.clientX, ey = e.clientY;
            $(this).css({
                '--ex': ex + 'px',
                '--ey': ey + 'px'
            });
        }).on('mouseup', function () {
            // crop
            // ...
            $(this).attr('style', '').off('mousemove').off('mouseup');
        });
    }
    else if (e.which == 3) {
        $(this).css('display', 'none');
    }
});

其中e.preventDefault()用于阻止默认事件(此处为拖动<img>元素)
在结束动作后通过$(this).attr('style', '')可以同时隐藏截图界面和清除设置的CSS变量

绘制选框

框选的区域为原图,未选的区域颜色加深,这种效果可以通过遮罩(mask)实现
mask的值mask-image接受一个图像,该值可以是gradient(渐变图像)
因此可以通过CSS函数linear-gradient()(线性渐变)绘制选框,参数说明参看MDN

#{
    mask: linear-gradient(to right, #000 40%, #0000 40%, #0000 60%, #000 60%);
    background: #0008;
}

这段代码创建了一个从左到右的渐变图像(渐变长度为0),同理可以创建一个从上到下(to bottom)的渐变图像,两者叠加即为选框
为了简洁,直接通过伪元素绘制选框,由于<img>元素没有伪元素(因为无内容物),用一个<div>将其包裹

<div id="screenshot">
    <img>
</div>
#screenshot::after {
    content: '';
    position: absolute;
    width: 100%;
    height: 100%;
    mask: linear-gradient(to right, #000 var(--sx), #0000 var(--sx), #0000 var(--ex), #000 var(--ex)),
        linear-gradient(to bottom, #000 var(--sy), #0000 var(--sy), #0000 var(--ey), #000 var(--ey));
    background: #0008;
}
  • 在Chrome中需要使用属性名-webkit-mask

裁剪图像

选定裁剪范围后需要再次绘制到<canvas>之后再生成图片
使用CanvasRenderingContext2D.drawImage()方法在绘制时进行裁剪,参数说明参看MDN

// crop
var cvs = $('<canvas></canvas>')[0];
var ctx = cvs.getContext('2d');
var dx = Math.abs(ex - sx), dy = Math.abs(ey - sy);
cvs.width = dx, cvs.height = dy;
ctx.drawImage($(this).children()[0], sx, sy, dx, dy, 0, 0, dx, dy);
cvs.toBlob(function (blob) {
    var url = URL.createObjectURL(blob);
    // download
    // ...
});

下载截图

创建<a>并通过模拟点击事件进行下载

// download
var a = $('<a></a>').attr({
    'href': url,
    'download': screenshot
});
a[0].click();
  • 如果不设置download(文件名),浏览器的行为将是打开该链接,而不是下载

示例

点击demo左上角截图按钮


添加新评论