图片裁剪

选择一个图片,然后载入到img中,有一个固定的选框,选取图片的一部分,可以拖动图片改变选取的部分.

这个操作经常出现在上传头像时,需要头像图片是一个指定比例或者长度宽度的图片.

实现这个功能关键在于计算图片在选框区域内的坐标点.然后调用ctx.drawImage()这个方法,把选区的图片画到canvas里.

dom

1. div容器,设置相对定位,拖动图片的操作在这个容器内完成.

2. img,在div内,绝对定位(坐标0,0),选择的图片显示在这个img中.

3. div固定选区,在div内,绝对定位(位于div容器中央),img在此选区范围内的部分就是选择目标,画到canvas里

4. canvas,在div下面,就是显示选区的图片.

操作

用鼠标拖动图片,观察canvas变化.手机端可以触摸拖动

事件

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

也可以绑定在img上.不过绑在容器上似乎感觉更好,在容器上移动鼠标,可以改变容器里图片的位置.

观察拖动

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

观察拖动一个图片,首先有个起点,然后是移动,最后停下来有个终点.这样就完成了一次拖动.

于是就有个概念,这一次拖动是相对于这一次的起点坐标的.如果进行下一次拖动,那么上一次拖动的终点坐标,就是这下一次拖动的起点坐标.

坐标计算

根据这个观察,在DOM准备好以后,图片在没有进行拖动前,有一个起始坐标img0,就是(0,0).坐标参考是div容器,这个不变.

鼠标起点坐标通过mousedown或者touchstart事件获得,因为开始拖动时,一般要按住这个图片,那么自然就触发按下事件.

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

    startX = mx0;
    startY = my0;

然后鼠标拖动,发生mousemove事件,鼠标坐标在变化,得到新坐标(mx1,my1)

这个坐标减去起点坐标,就是鼠标移动的距离,把这个距离加到图片坐标上,就是图片这次移动后的新坐标位置.img1

    // 鼠标移动距离
    moveX = mx1 - startX;
    moveY = my1 - startY;
    // 带入startX,startY
    moveX = mx1 - mx0;
    moveY = my1 - my0;
    // 图片移动的目标坐标,就是图片起始位置加上鼠标移动距离
    img1X = img0.offsetLeft + moveX;
    img1Y = img0.offsetTop + moveY;
    // 代入moveX,moveY
    img1X = img0.offsetLeft + mx1 - mx0;
    img1Y = img0.offsetTop + my1 - my0;

根据这个推理,需要在鼠标按下事件里记录鼠标起始位置还有图片起始位置

    // 鼠标起始坐标
    let mxy = { x: 0, y: 0 };
    // 图片起始坐标
    let imgxy = { x: imgele.offsetLeft, y: imgele.offsetTop };
    // 鼠标按下时
    function moveStart(x, y) {
      mxy = { x: x, y: y };
      imgxy = { x: imgele.offsetLeft, y: imgele.offsetTop };
    }

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

    function moving(x, y) {
      imgele.style.left = (imgxy.x + x - mxy.x) + 'px';
      imgele.style.top = (imgxy.y + y - mxy.y) + 'px';
    }

坐标计算2

第二种思路是刚开始想象中的那种,鼠标拖动的坐标位置就是图片的新坐标.实现它的关键在于将鼠标坐标和图片坐标视为一点.这样,改变鼠标坐标就相当于改变图片坐标.

移动图片实际上是在修改图片左上角顶点坐标,如果将鼠标坐标变化与这个点产生关系,那么就实现移动鼠标就等于移动图片了.

鼠标按下时,起始坐标mxy,图片起始坐标imgxy.如果总是以图片为原点(0,0),那么鼠标在这个坐标系的坐标就是

    startX = mx0 - imgele.offsetLeft;
    startY = my0 - imgele.offsetTop;

然后鼠标拖动,发生mousemove事件,鼠标坐标在变化,得到新坐标(mx1,my1)

这个坐标减去起点坐标,就是图片新坐标.img1

    // 鼠标移动后,图片新坐标
    img1X = mx1 - startX;
    img1Y  = my1 - startX;
    // 代入startX,Y
    img1X = mx1 - mx0 + imgele.offsetLeft;
    img1Y = my1 - my0 + imgele.offsetTop;

结果发现两种思路得到的结果是相同的,如果只是从公式的数学推演角度看的化.第2种思路实现程序时,相对简洁一些.

在鼠标按下时,记录鼠标坐标,在以图片为原点的坐标系的坐标

    // 鼠标起始坐标
    let mxy = { x: 0, y: 0 };   
    // 鼠标按下时
    function moveStart(x, y) {
      mxy.x = x - imgele.offsetLeft;
      mxy.y = y - imgele.offsetTop;
    }

在鼠标移动事件里计算图片新坐标,也就是鼠标的新坐标

    function moving(x, y) {
      imgele.style.left = (x - mxy.x) + 'px';
      imgele.style.top = (y - mxy.y) + 'px';
    }

画选区图片

这个就是计算选区DIV的坐标,当图片在(0,0)点时,选区坐标就是选区在图片上的选取点坐标.

图片不在(0,0)时,选取点坐标就是,选区DIV坐标减去图片坐标.

    //  获取div选区坐标
    let squarexy = { x: selsquareDIV.offsetLeft, y: selsquareDIV.offsetTop };
    // 获取图片坐标
    let imgxy = { x: imgele.offsetLeft, y: imgele.offsetTop }
    // 绘画
    ctx.clearRect(0, 0, cavW, cavH);
    ctx.drawImage(imgele, squarexy.x - imgxy.x, squarexy.y - imgxy.y, cavW, cavH, 0, 0, cavW, cavH);

测试

my camera photo