canvas裁剪图片
图片裁剪
选择一个图片,然后载入到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);