图片裁剪

功能

将一个图片裁剪成需要的大小,例如在上传头像时需要头像图片是一个指定比例或者长度宽度的图片.

实现

  • 使用canvas做图片裁剪框.指定长宽.
  • 使用一个img图片做图片源,可以通过文件选择框选择图片,本例子是直接引用了一个图片.
  • 在canvas元素的范围内,绑定鼠标或者触摸事件,移动鼠标时,根据图片原始坐标和鼠标移动距离,重新绘制图片.
  • 有一个缩放功能条,可以在图片原本宽度(最大),和canvas宽度(最小)之间缩放.
  • 图片加载成功时,画到canvas里.默认绘制宽度缩放到canvas的宽度,如果图片宽度小于canvas宽度,则不缩放.

事件

在canvas上绑定鼠标按下(mousedown)和移动事件(mousemove),移动端对应事件是touchstart,touchmove

拖动行为

经过一些尝试,发现图片坐标计算不是想象中的这种:鼠标坐标点(x,y)就是图片的目的坐标点的情况,这个认知错误.关键点在于没有认清拖动这个行为.

观察拖动一个图片:在拖动之前,图片有个起始坐标,然后是鼠标在拖动,鼠标拖动开始时(鼠标按下)有个起点坐标,最后停下来(鼠标放开)有个终点坐标.这样是完成了一次拖动.

图片的终点坐标是起始坐标加上鼠标移动的距离.图片的终点坐标,自然会成为下一次拖动的起始坐标.

那么这个拖动过程里,有4个变量: 图片起点坐标,鼠标起点坐标,鼠标终点坐标,图片终点坐标.

坐标计算

根据这个观察,图片在开始进行拖动前,有一个起始坐标imgBegin(x,y),初始设为(0,0).这个起始坐标,是指图片在canvas上绘制时的坐标.

鼠标起点坐标通过mousedown或者touchstart事件获得,因为开始拖动时,要先按住这个图片,会触发鼠标按下事件.

此时得到了鼠标按下时的坐标mStart(x,y),这个坐标是鼠标的起始坐标.

  
    // 1. 图片起始坐标,初始为0,0.(就是绘制时在canvas上的坐标)
    imgBegin = {x : 0, y : 0};

    // 2. 图片终点坐标,初始为0,0
    imgTo = { x: 0, y: 0 };
    
    // 3. 鼠标按下时,设置鼠标坐标
    mouseDown = (x, y) => {
      mStart.x = x;
      mStart.y = y;

      // 图片起点坐标为上次拖动结束时的坐标
      imgBegin.x = imgTo.x;
      imgBegin.y = imgTo.y;
    }


鼠标按下后拖动,发生mousemove事件,鼠标坐标在变化,得到新坐标(mMoveX,mMoveY).在拖动进行中,坐标是时时变化的.

这个坐标减去起点坐标,就是鼠标移动的距离,把这个距离加到图片起始坐标,就是图片这次移动后的新坐标位置.imgTo(x, y)

在鼠标移动事件里计算鼠标移动距离和图片新坐标

    
    mousemove = (x, y) => {
    
      // 4. 鼠标移动的距离
      let dX = x - mStart.x;
      let dY = y - mStart.y;

      // 图片终点坐标
      imgTo.x = imgBegin.x + dX;
      imgTo.y = imgBegin.y + dY;

      // 绘图
      ReDrawCanvas(imgTo.x, imgTo.y);
    }


缩放

缩放是指保持图片比例缩放.缩放比例最大为100%,就是原始图片大小.最小为canvas画布大小,比例计算是: scale = cavWidth / imgWidth

放大缩小按钮在这个范围内缩放图片,每次缩放幅度为5%.

缩放的思路推导: (假定绘制点0,0)

  • 如果缩放为100%,(就是不缩放),这是最简单情况,就是整个图绘制: ctx.drawImage(imgele, 0, 0);
  • 缩放n%时,假如缩放30%,图片宽和高要变成原大小的30%,然后绘制: ctx.drawImage(imgele, 0, 0, imgWidth*30%, imgHeight*30%);
  • 图片在canvas上绘制的目标大小,就是图片缩放后的大小.由于canvas的drawImage()方法可以进行缩放绘制,指定了绘制区域参数时,会缩放绘制.所以只需计算图片缩放后的宽高.

canvas上绘制的坐标和大小参数说明:

    
    // imgTo.x, imgTo.y是图片在canvas的绘制点坐标.(图片终点坐标)
    // imgScale是缩放比例,canvas的宽度和图片宽度的比值  cavWidth / imgWidth
    // imgW * imgScale 和 imgH * imgScale 是图片在缩放比例下的宽度和高度
    // 也就是图片绘制在canvas上的目标大小
    ctx.drawImage(imgele, imgTo.x, imgTo.y, imgW * imgScale, imgH * imgScale);


程序逻辑过程

  • 使用input file选择图片文件
  • input file的onchange事件里获取file图片文件对象
  • 使用FileReader对象载入图片对象,使用readAsDataURL(file)方式,得到的base64结果
  • 注册FileReader对象的load事件,图片载入完成时,会触发该事件.
  • 该事件方法里建立img对象,设置img对象的onload事件和src属性.src的值是FileReader.result
  • img载入完成时,触发onload事件,在这个事件里将img作为源图片,画到canvas上.
  • 使用canvas的drawImage重绘图片,调整坐标参数和绘制比例.
  • canvas的toBlob方法保存裁剪后的图片.

测试

用鼠标在canvas上拖动,手机端是触摸拖动.调整图片位置.

调节缩放条,调整图片大小

缩小 -放大 +复位
my camera photo