基础裁剪
照片裁剪一个很常见的功能了,今天咱们来手撸一个耍耍看。?
当前,照片裁剪在很大程度上已经转向基于 Canvas
来实现,这样做有几个好处:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
- 性能:
Canvas
能通过GPU加速进行图像操作,这通常比CPU处理更快。 - 灵活性:
Canvas API
提供了丰富的绘图和图像处理能力,可以轻松实现复杂的裁剪、旋转、缩放等操作。 - 易于集成、无需依赖:
Canvas
可以轻易无缝集成到各种库/框架(如React/Vue
等)中,不需要安装额外的插件。 - ...
咱们来看一个最简单的裁剪案例:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
html文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
<!DOCTYPE html>
<html>
<head>
<style>
canvas { border: 1px solid #ccc; margin-top: 10px; }
</style>
</head>
<body>
<input id="file" type="file">
<canvas id="canvas"></canvas>
<canvas id="canvasCropping"></canvas>
<script>
document.addEventListener('DOMContentLoaded', () => {
const file = document.getElementById('file');
const canvas = document.getElementById('canvas');
const canvasCtx = canvas.getContext('2d');
const canvasCropping = document.getElementById('canvasCropping');
const canvasCroppingCtx = canvasCropping.getContext('2d');
const image = new Image();
file.addEventListener('change', (e) => {
const file = e.target.files[0];
image.src = URL.createObjectURL(file);
image.onload = () => {
// 将画布大小调整成照片大小
const { width, height } = image;
canvas.width = width;
canvas.height = height;
// 将图片绘制到画布中
canvasCtx.drawImage(image, 0, 0, width, height);
// 裁剪出坐标为(0, 0),width与height都为100的照片
const croppingImageData = canvasCtx.getImageData(0, 0, 100, 100);
// 将裁剪到的照片绘制到新的画布中预览
canvasCroppingCtx.putImageData(croppingImageData, 0, 0)
};
})
});
</script>
</body>
</html>
很简单,咱们上传了一个照片,将它完整的绘制在左边的 Canvas
中,然后进行裁剪,将裁剪得到的照片绘制到了右边的 Canvas
中。 ?文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
效果如下:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
你可以直接在右边的 Canvas
上鼠标右键将照片下载下来,也可以通过代码,去调用 Canvas
的 toDataURL 方法,将裁剪的照片下载下来了,这样就能得到你想要的裁剪照片了。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
从上面代码中,可以看到咱们是调用了两个 Canvas
的相关API来完成裁剪的,如下:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
ctx.getImageData(x, y, width, height):用于获取画布中的某个区域内容,能得到一个 ImageData 对象。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
ctx.putImageData(imagedata, x, y):能将 ImageData
对象绘制到画布中。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
裁剪器裁剪
平常的照片裁剪功能一般都是有一个裁剪器,它能帮助我们裁剪需要的部分,正好,咱们在上一篇文章 ???图片裁剪器✂✂✂ 已经完成了裁剪器这个功能。如下:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/17147.html
这里咱们就直接拿过来用用,裁剪器源码可以看这里。传送门 ???
来看看这个裁剪器要如何与具体裁剪功能结合起来:
html
<button id="btn">裁剪</button>
<canvas id="canvasCropping"></canvas>
js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// currentDimention: top/right/bottom/left
if (currentDimention.length > 0) {
// 图片的宽度与高度
const { width: imageWidth, height: imageHeight } = bgImage.getBoundingClientRect();
// 创建一个canvas用于裁剪操作,其实也可以将bgImage本身变成一个canvas,直接在上面操作就行
const canvas = document.createElement('canvas');
const canvasCtx = canvas.getContext('2d');
canvas.width = imageWidth;
canvas.height = imageHeight;
// 绘制照片
canvasCtx.drawImage(bgImage, 0, 0, imageWidth, imageHeight);
// 裁剪照片
const x = currentDimention[LEFT];
const y = currentDimention[TOP];
const width = imageWidth - currentDimention[LEFT] - currentDimention[RIGHT];
const height = imageHeight - currentDimention[TOP] - currentDimention[BOTTOM];
const croppingImageData = canvasCtx.getImageData(x, y, width, height);
// 通过canvas展示裁剪的照片
const canvasCropping = document.getElementById('canvasCropping');
const canvasCroppingCtx = canvasCropping.getContext('2d');
canvasCropping.width = croppingImageData.width;
canvasCropping.height = croppingImageData.height;
// 清空一下画布
canvasCroppingCtx.clearRect(0, 0, croppingImageData.width, croppingImageData.height);
//将裁剪到的照片绘制到新的画布中预览
canvasCroppingCtx.putImageData(croppingImageData, 0, 0);
}
})
上面仅是裁剪功能的代码,关于 currentDimention/bgImage/LEFT...
等变量是什么,最好还是要先去看看上一篇文章 ???图片裁剪器✂✂✂ 的内容。
其实,咱们主要目的就是为了得到 ctx.getImageData(x, y, width, height) API所需的参数信息,裁剪器的目的也是如此。
还有,由于是不断往 Canvas
上绘制新裁剪照片,每次绘制之前记得清空一下画布哦?:ctx.clearRect(x, y, width, height)。
效果如下:
固定规格裁剪
固定规格裁剪,这应该算是裁剪器那边的小功能,等于就是提前固定了裁剪器的一些规格尺寸。
效果如下:
实现也很简单:
html
<button id="btn-small">小</button>
<button id="btn-middel">中</button>
<button id="btn-big">大</button>
javascript
function createCropper() {
mask.style.display = "block";
cropper.style.display = "block";
setDimention(initDimention);
// 增加这行
currentDimention = initDimention;
// ...
}
btnSmall.addEventListener("click", () => {
currentDimention = [150, 150, 150, 150];
setDimention(currentDimention);
})
btnMiddel.addEventListener("click", () => {
currentDimention = [100, 100, 100, 100];
setDimention(currentDimention);
})
btnBig.addEventListener("click", () => {
currentDimention = [10, 10, 10, 10];
setDimention(currentDimention);
})
这部分其实是属于一个铺垫?抛砖引玉?? (反正就是为了继续引出下文内容吧。)
不规则裁剪
是不是有点意犹未尽?咋固定规格全是矩形?能否搞个圆形?三角形?或者其他形状?
当然是可以的,接下来就要来聊聊不规则的裁剪。
要实现不规则裁剪,会用到很多 Canvas
相关的API的,其中最关键的就是 ctx.clip() ,它是裁剪的关键。
先来看看实现效果:
具体实现:
html
<!DOCTYPE html>
<html>
<body>
<input id="file" type="file" />
<canvas id="canvas"></canvas>
<script>
document.addEventListener('DOMContentLoaded', () => {
const file = document.getElementById("file");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const width = 600;
const height = 400;
const image = new Image();
// 照片上传后绘制到canvas中展示
file.addEventListener("change", (e) => {
const target = e.target.files[0];
const imgURL = URL.createObjectURL(target);
image.src = imgURL;
image.onload = () => {
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0, width, height);
}
});
// 裁剪路径集合
const pathMap = [];
// 裁剪中
let cropping = false;
// 监听canvas的鼠标按下事件
canvas.addEventListener('mousedown', e => {
cropping = true;
pathMap.push({
// 点和线的坐标不能一样
offsetXPoint: e.offsetX,
offsetYPoint: e.offsetY,
offsetY: e.offsetY,
offsetY: e.offsetY,
});
draw();
});
// 监听canvas的鼠标移动事件
canvas.addEventListener('mousemove', (e) => {
if (cropping) {
// 拿最后一个点,根据鼠标移动不断改变偏移量
pathMap[pathMap.length - 1].offsetX = e.offsetX;
pathMap[pathMap.length - 1].offsetY = e.offsetY;
// 标记是有效的点
pathMap[pathMap.length - 1].effectived = true;
}
});
// 监听canvas的鼠标双击事件
canvas.addEventListener('dblclick', (e) => {
cropping = false;
if (!pathMap[pathMap.length - 1].effectived) {
// 删除双击带来的多余点
pathMap.pop();
}
// 裁剪照片
setTimeout(async () => {
// 清空原画布
ctx.clearRect(0, 0, width, height);
// 开始绘制
ctx.beginPath();
pathMap.forEach((item, index) => {
if (index === 0) {
ctx.moveTo(item.offsetXPoint, item.offsetYPoint);
}
ctx.lineTo(item.offsetX, item.offsetY);
});
// 裁剪目标区域
ctx.clip();
// 在裁剪的区域内绘制图片
ctx.drawImage(image, 0, 0, width, height);
}, 200);
});
/** @name 绘制 **/
function draw() {
// 先清空画布
ctx.clearRect(0, 0, width, height);
// 绘制照片
ctx.drawImage(image, 0, 0, width, height);
// 开始绘制
ctx.beginPath();
// 设置画笔的颜色
ctx.strokeStyle = 'yellow';
pathMap.forEach((item, index) => {
if (index === 0) {
// 端点位置
ctx.moveTo(item.offsetXPoint, item.offsetYPoint);
}
ctx.lineTo(item.offsetX, item.offsetY);
})
// 将路径绘制到画布上
ctx.stroke();
// 通过requestAnimationFrame去不断执行绘制
cropping && requestAnimationFrame(draw);
}
});
</script>
</body>
</html>
案例代码不多,但需要你足够了解 Canvas
的知识才行,特别得多关注一下 ctx.moveTo 与 ctx.lineTo
这两个API可以瞅一瞅这个图:
应该足够说明两者的作用了。
当然,这还没完?,不是说要裁剪个圆形吗?
其实当你理解上面的案例后,这个问题就是手到擒来了,比如:
javascript
canvas.addEventListener('mousedown', e => {
// 圆形
ctx.clearRect(0, 0, width, height);
ctx.beginPath();
ctx.arc(width/2, height/2, 100, 0, Math.PI * 2);
ctx.clip();
ctx.drawImage(image, 0, 0, width, height)
});
咱们稍微修改一下 mousedown
事件的逻辑,效果如下:
链接:https://juejin.cn/post/7391160093565976616
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论