Canvas实现一个上传图片截图功能

1. 前言

在我们平时开发图片上传时,有时需要实现图片的裁剪功能。通常我们使用Vue-cropper来实现图片的裁剪功能。

如何实现呢?本文介绍下图片裁剪的功能的实现过程。

2. 实现思路

  1. 使用 input 元素让用户选择图片文件,然后使用 FileReader API 读取图片文件的内容,并将其设置为 img 元素的 src 属性。
  2. 创建一个 canvas 元素并获取其 2D 渲染上下文,然后使用 drawImage 方法将图片绘制到 canvas 上。
  3. 监听鼠标的 mousedownmousemovemouseup 事件,用于创建和调整裁剪框。当用户点击鼠标并移动时,记录鼠标的位置并在 canvas 上绘制一个矩形作为裁剪框。
  4. 当用户释放鼠标按钮时,使用 getImageData 方法获取裁剪框内的像素数据,然后创建一个新的 canvas 并使用 putImageData 方法将这些像素数据放入新的 canvas 中。这样就得到了裁剪后的图片。
  5. 最后,使用 toDataURL 方法将裁剪后的图片转换为 Base64 格式的字符串,或者使用 toBlob 方法将其转换为 Blob 对象,然后可以将其下载到用户的计算机上,或者上传到服务器。

3. 实现步骤

使用原生js进行实现,使用两个canvas,一个是原图展示,一个是截图后的。

HTML部分:

html 复制代码
<!-- 文件上传 -->
<input type="file" id="inputFile" accept="image/*" />
<!-- 展示读取的图片的canvas -->
<div class="clipContainer">
    <canvas id="clip"></canvas>
</div>
<!-- 截图后的图片的canvas -->
<div class="drawContainer">
    <canvas id="draw"></canvas>
</div>

JS公共部分:

js 复制代码
const inputNode = document.querySelector("#inputFile");
const clipCanvas = document.querySelector("#clip");
const drawCanvas = document.querySelector("#draw");
const clipContainer = document.querySelector(
    ".clipContainer"
);
const drawContainer = document.querySelector(
    ".drawContainer"
);

const clipCanvasCtx = clipCanvas.getContext("2d");
const drawCanvasCtx = drawCanvas.getContext("2d");

const imageNode = document.createElement('img');
const drawImageNode = document.createElement('img');//用于最终展示图片
let resultFile = null;//最终生成的File对象图片文件

3.1 选择文件进行预览

使用FileReader读取文件的url和宽高,进行cavas绘制。

js 复制代码
// 1. 选择图片 读取图片
inputNode.addEventListener(
    "change",
    function (e) {
        const file = e.target.files[0];
        const reader = new FileReader();
        reader.onload = function (e) {
            imageNode.src = e.target.result;
            imageNode.onload = function () {
                // 获取真实的图片宽高  canvas绘制图片
                generateCanvas(clipContainer, clipCanvas, this.width, this.height);
                clipCanvasCtx.drawImage(imageNode, 0, 0, this.width, this.height);

            };
        };
        file && reader.readAsDataURL(file);
    },
    false
);

// 2. canvas绘制图片
function generateCanvas(canvasContainer, canvas, width, height) {
    // 2.1 外部容器的宽高
    canvasContainer.style.width = width + 'px';
    canvasContainer.style.height = height + 'px';
    // 2.2 canvas的宽高
    canvas.width = width;
    canvas.height = height;
}

结果如下:

3.2 监听mousedown事件

监听mousedown 记录起点坐标,使用startPositon进行记录起始位置。

js 复制代码
// 记录点击开始结束位置
let startPosition = []

clipCanvas.addEventListener('mousedown', function (e) {
    // 3.1 记录开始截图的位置
    startPosition = [e.offsetX, e.offsetY]
    // 3.2 开始监听 mousemove 和 mouseup 事件
    clipCanvas.addEventListener('mousemove', mousemoveHandler, false)
    clipCanvas.addEventListener('mouseup', mouseupHandler, false)
})

3.3 监听mousemove事件

记录鼠标移动坐标,计算出截图大小,绘制截图蒙层区域

js 复制代码
// 记录截图的偏移x,y和截图的宽高w,h
let screenShotData = [] 

function mousemoveHandler(e) {
    // 1. 计算裁剪区域的宽度和高度
    const { offsetX, offsetY } = e
    const [startX, startY] = startPosition
    // 偏移位置-起始位置 = 真实宽高
    const [rectWidth, rectHeight] = [offsetX - startX, offsetY - startY]
    // 2. 保存裁剪区域的相关信息
    screenShotData = [startX, startY, rectWidth, rectHeight]
    // 3. 重新绘制
    fill(clipCanvasCtx, clipCanvas, screenShotData)
}

function fill(ctx, canvas, sizeData) {

    const { width, height } = canvas
    const [x, y, w, h] = sizeData
    // 1. 再次绘制前,清理canvas1画布上的内容
    ctx.clearRect(0, 0, width, height)

    // 2. 画布上绘制蒙层
    ctx.fillStyle = `rgba(0,0,0,0.6)`
    ctx.fillRect(0, 0, width, height)

    // 3. 绘制裁剪区域
    // 在源图像外绘制新图像,只有源图像外的目标图像部分会被显示,源图像是透明的
    ctx.globalCompositeOperation = 'destination-out'
    ctx.fillStyle = '#2c2c2c'
    ctx.fillRect(...sizeData)

    // 设置在现有画布上绘制新的图形
    ctx.globalCompositeOperation = 'destination-over'
    // 剪切图像,并在画布上定位被剪切的部分
    ctx.drawImage(imageNode, 0, 0, width, height, 0, 0, width, height);
}

结果如下:

3.4 监听mouseup事件

注销事件监听,将裁剪区域放入另一个canvas中进行处理,并读取File对象

js 复制代码
function mouseupHandler(e) {
    // 1. 注销监听 mousedown 和 mousemove 事件
    clipCanvas.removeEventListener('mousemove', mousemoveHandler, false)
    clipCanvas.removeEventListener('mouseup', mouseupHandler, false)

    // 2. 开始绘制截图区域图片
    const data = clipCanvasCtx.getImageData(...screenShotData)

    // 3. 使用新画布
    const [x, y, w, h] = screenShotData
    generateCanvas(drawCanvas, drawContainer, w, h)

    // 每次绘制前,都先进行清除操作
    drawCanvasCtx.clearRect(...screenShotData);
    // 将 clipCanvas 裁剪区域的数据放入 drawCanvas 中
    drawCanvasCtx.putImageData(data, 0, 0);
    drawCanvas.style.display = 'none'

    // 4. 展示图片
    const imageUrl = drawCanvas.toDataURL('image/png')
    drawImageNode.src = imageUrl;
    document.body.appendChild(drawImageNode)

    // 5. 生成File对象
    drawCanvas.toBlob(blob => {
        resultFile = new File([blob], 'test.png', { type: 'image/png' })
    }, 'image.png')
}

4. 总结

最后总结一下如图:

通过本次实践,了解截图的运行过程。希望能对大家有所帮助,如有错误,请指正O^O!


参考文章:

使用 Canvas 实现基本的截图功能

W3C Canvas

MDN drawImage

相关推荐
胡桃夹夹子2 分钟前
【前端优化】vue2 webpack4项目升级webpack5,大大提升运行速度
前端·javascript·vue.js·webpack·性能优化
Stringzhua7 分钟前
JavaScript【7】BOM模型
开发语言·前端·javascript
阿幸软件杂货间23 分钟前
谷歌浏览器(Google Chrome)136.0.7103.93便携增强版|Win中文|安装教程
前端·chrome
繁依Fanyi1 小时前
Animaster:一次由 CodeBuddy 主导的 CSS 动画编辑器诞生记
android·前端·css·编辑器·codebuddy首席试玩官
想起你的日子1 小时前
Android studio 实现弹出表单编辑界面
java·前端·android studio
LuckyLay2 小时前
Vue百日学习计划Day9-15天详细计划-Gemini版
前端·vue.js·学习
水银嘻嘻8 小时前
12 web 自动化之基于关键字+数据驱动-反射自动化框架搭建
运维·前端·自动化
小嘟嚷ovo8 小时前
h5,原生html,echarts关系网实现
前端·html·echarts
十一吖i9 小时前
Vue3项目使用ElDrawer后select方法不生效
前端
只可远观9 小时前
Flutter目录结构介绍、入口、Widget、Center组件、Text组件、MaterialApp组件、Scaffold组件
前端·flutter