使用Canvas实现图片裁剪

一、概述

Canvas API 提供了一个通过JavaScript 和 HTML的 canvas 元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。

Canvas 最有意思的一项特性是针对图像的操作能力,可以动态的合成图像、制作图形的背景,支持浏览器的各种图片格式,甚至可以使用同一页面中其他canvas元素生成的图片作为图片源。

【引入图像到canvas】:

  • 获得一个指向HTMLImageElement的对象或者另一个canvas元素的引用作为源,也可以通过URL的方式来使用图片
  • 使用drawImage()函数将图片绘制到画布上

【canvas API图片源的来源】:
1、HTMLImageElement:Image()函数构造出来的,或者任何的img元素
2、HTMLVideoElement:video元素,从视频中抓取当前帧作为一个图像
3、HTMLCanvasElement:canvas元素
4、ImageBitmap:位图,可以通过上述源生成

二、绘制图片

在获取到源图像对象后,就可以使用drawImage方法将源图像渲染到canvas里。

/**
    image: img 或者 canvas对象
    x:在canvas中起始的横坐标
    y:在canvas中起始的纵坐标
*/
drawImage(image, x, y) 
// 例子
function draw() {
    var ctx = document.getElementById("canvas").getContext("2d");
    var img = new Image();
    img.onload = function() {
        ctx.drawImage(img, 0, 0);
        ctx.beginPath();
        ctx.moveTo(30, 96);
        ...
    }
    img.src="http://xxx.png"
}

三、缩放Scaling

drawImage 方法的又一变种是增加了两个用于控制图像在 canvas 中缩放的参数。

/**
    image: img 或者 canvas 对象
    x,y: 坐标的起始位置
    width,height: 向canvas画入时应该缩放的大小
**/
drawImage(image, x, y, width, height)

四、切片Slicing

drawImage 方法的第三个方式是有8个新参数,用于控制做切片显示的

drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。其他8个中前4个是定义图像源的切片位置和大小,后4个则是定义切片的目标显示位置和大小。

/**
    相框的例子
**/
<html>
 <body onload="draw();">
   <canvas id="canvas" width="150" height="150"></canvas>
   <div style="display:none;">
     <img id="source" src="https://mdn.mozillademos.org/files/5397/rhino.jpg" width="300" height="227">
     <img id="frame" src="https://mdn.mozillademos.org/files/242/Canvas_picture_frame.png" width="132" height="150">
   </div>
 </body>
</html>

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  // Draw slice
  ctx.drawImage(document.getElementById('source'),33,71,104,124,21,20,87,104);

  // Draw frame
  ctx.drawImage(document.getElementById('frame'),0,0);
}

五、通过canvas实现图片上传裁剪

在项目中会遇见头像更新等功能,根据上传的图片实现固定的尺寸裁剪之后上传至服务端;具体的流程为:
1、获取图片:本地图片上传 new FileReader对象 或者 图片链接 new Image对象
2、canvas绘制图片
3、canvas裁剪图片
4、输出裁剪图片toBlob
5、上传至服务端

// 1.上传图片;通过监听input的onChage事件来回去file对象

<input type="file" accept="image/*" onChange={this.handleChange} />

const handleChange = (e) => {
    const file = Array.from(e.target.value);
    // 可以检验图片的大小和格式操作
    // 在每次上传之后,记得将input的value置空,防止再次上传相同图片无法触发onchange事件
}

// 2.canvas绘制图片
// 首先先解析图片,使用FileReader
const fileInfo = (file) => {
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function(e) {
        let img = new Image();
        img.onload = function() {
            ...// 处理图片的高宽等信息
        }
        img.src = e.target.result
    }
}
//预览图片
使用drawImage 绘制图片到canvas上

// 3、裁剪操作
初始化配置:
const initConfig = () => {
    this.ctx = document.getElementById("canvas").getContext("2d");
    this.draging = false;
    this.startX = null;
    this.startY = null;
}
// 鼠标点击事件
const mouseDown = (e) =>
    this.draging = true;
    this.startX = e.nativeEvent.offsetX;
    this.startY = e.nativeEvent.offsetY;
}
// 移动鼠标,绘制裁剪框
const mouseMove = (e) => {
    if (!this.draging) return;
    let tmpWid = e.nativeEvent.offsetX-startX,
        tmpHig = e.nativeEvent.offsetY-startY;
    // 开始绘制裁剪框
    drawTrim(startX, startY, tmpWid, tmpHig)
}
//松开鼠标
const mouseOut = (e) => {
    if (this.draging) {}
    this.draging = false;
}

裁剪框的绘制主要分为:1、绘制蒙层阴影;2、裁剪框部分镂空;3、将图片绘制在蒙层下方

const drawTrim= (startX, startY, width, height) => {
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    // 消除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 蒙层
    ctx.save();
    ctx.fillStyle='rgba(0,0,0,0.5)';
    ctx.fillRect(0, 0, canvas.width, canvas.height)

    // 将蒙层凿开,变成透明
    ctx.globalCompositeOperation = 'source-atop';
    ctx.clearRect(startX, startY, width, height); // 裁剪框

    ctx.restore();

    //将图片绘制在蒙层下面
    ctx.save();
    ctx.globalCompositeOperation = 'destination-over';
    ctx.drawImage(this.image, 0, 0, canvas.width, canvas.height);
    ctx.restore();
}

最后将裁剪框内的图像输出图片,有两种方式:

  • canvas.toDataURL()
  • canvas.toBlob()

再此之前需要将裁剪的部分绘制到新的canvas画布上;

const uploadImg = (type) => {
    this.canvas.toBlob((blog) => {
        blob.lastModifiedDate = new Date();
        let fd = new FormData();
        fb.append('image', blob);
    })
}