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

相关推荐
CDN3604 分钟前
高防切换后网站打不开?DNS 解析与回源路径故障排查
前端·网络·数据库
信也科技布道师15 分钟前
把7个页面变成1段对话:AI如何重构借款流程
前端·人工智能·重构·架构·交互·用户体验
276695829230 分钟前
携程旅行 token1005
java·linux·前端·javascript·携程旅行·token1005·携程酒店
freewlt36 分钟前
Cursor与AI编程工具崛起:前端工程师的能力模型重构与职业生存策略
前端·重构·ai编程
墨雪遗痕44 分钟前
工程架构认知(三):从传统Web系统到AI大模型驱动系统
前端·人工智能·架构
C澒1 小时前
AI 生码 - PRD2CODE:Schema2PRD 全流程设计与实现
前端·ai编程
掘金者阿豪1 小时前
微信图片已过期或已被清理,真的找不回了吗?完整自救指南
前端·后端
颜酱1 小时前
从 DeepSeek 文本对话到流式输出
前端·javascript·人工智能
Ulyanov1 小时前
打造现代化雷达电子对抗仿真界面 第二篇:雷达电子对抗仿真系统核心功能实现
前端·python·信息可视化·数据可视化·系统仿真·雷达电子战
快乐小土豆~~1 小时前
上传视频时截取正脸照片
前端·音视频·vladmandic