Appearance
图片裁剪
本次将通过 canvas 实现图片裁剪的功能,上传文件后本地加载图片并渲染图片与裁剪框,裁剪框可以在图片内部中移动(记录裁剪的偏移量、大小),最终通过 canvas 的 api 实现裁剪功能,最后导出成 blob 数据。
实现步骤
- 通过文件域将图片上传
- 通过
URL.createObjectURL
将图片通过 img 渲染出来 - 在图片上铺满遮罩层,将裁剪框放置在遮罩层中,并通过事件交互设置裁剪框大小以及偏移量
- 设置好裁剪区域后,记录下裁剪框的偏移量、尺寸,计算出当前图片与图片原始尺寸(
img.natureWidth
)的缩放比,因为 canvas 渲染图片是直接渲染的图片原始尺寸,所以需要计算出缩放比例,后续 api 需要用到。 - 通过 ctx.drawImgae(img,图片原始 left 偏移量,图片原始 top 偏移量,图片原始裁剪宽度,图片原始裁剪高度,渲染到 canvas 的 left 偏移量,渲染到 canvas 的 top 偏移量,渲染出的宽度,渲染出的高度)渲染图片。其中图片原始 left 偏移量、图片原始 top 偏移量、图片原始裁剪宽度,图片原始裁剪高度,需要通过计算得出的。我们知道缩放比、以及缩放后的偏移量、缩放后的尺寸,可以计算出原始的偏移量、尺寸。 计算方法为: 原始 left 偏移量:当前 left 偏移量/缩放比例 原始 top 偏移量:当前 top 偏移量/缩放比例 原始裁剪的 width:当前裁剪框 width/缩放比例 原始裁剪的 height:当前裁剪框 height/缩放比例
- 最后通过 canvas.toBlob 将图片导出为二进制数据,当然也可以将 Blob 包装成 File 文件
关键步骤
通过 cavansd 的 drawImage 绘制原始图片,并裁剪出对应大小、位置的图片。 剪切图像,并在画布上定位被剪切的部分:
WARNING
由于drawImage
调用后,canvas 会将图片的原始尺寸渲染到画布中,而裁剪框记录的偏移量、大小都是根据实际图片来记录的,所以我们需要通过图片的原始尺寸、实际尺寸计算出缩放比例,也就是实际宽度/原始宽度
即可计算出缩放比例了,然后就可以context.drawImage(img, 裁剪框left偏移量/比例, 裁剪框裁剪框top宽度/比例, 裁剪框宽度/比例, 裁剪框高度/比例,画布输出left偏移量,画布输出top偏移量 , 画布输出宽度,画布输出高度);
js
context.drawImage(img, sx, sy, swidth, sheight, x, y, width, height);
/**
* img:要渲染哪个图片?
* sx:从这个图片的x轴上的某个值开始裁剪
* sy:从这个图片的y轴上的某个值开始裁剪
* swidth:从这个图片的x轴上裁剪多宽的内容
* sheight:从这个图片的y轴上裁剪多宽的内容
* x:将裁剪出来的图片输出到画布的x轴上的某一处
* y:将裁剪出来的图片输出到画布上的y轴上的某一处
* width:输出到画布上图片的宽度
* height:输出到画布上图片的高度
**/
context.drawImage(img, sx, sy, swidth, sheight, x, y, width, height);
/**
* img:要渲染哪个图片?
* sx:从这个图片的x轴上的某个值开始裁剪
* sy:从这个图片的y轴上的某个值开始裁剪
* swidth:从这个图片的x轴上裁剪多宽的内容
* sheight:从这个图片的y轴上裁剪多宽的内容
* x:将裁剪出来的图片输出到画布的x轴上的某一处
* y:将裁剪出来的图片输出到画布上的y轴上的某一处
* width:输出到画布上图片的宽度
* height:输出到画布上图片的高度
**/
参数值
参数 | 描述 |
---|---|
img | 规定要使用的图像、画布或视频。 |
sx | 可选。开始剪切的 x 坐标位置。 |
sy | 可选。开始剪切的 y 坐标位置。 |
swidth | 可选。被剪切图像的宽度。 |
sheight | 可选。被剪切图像的高度。 |
x | 在画布上放置图像的 x 坐标位置。 |
y | 在画布上放置图像的 y 坐标位置。 |
width | 可选。要使用的图像的宽度。(伸展或缩小图像) |
height | 可选。要使用的图像的高度。(伸展或缩小图像) |
js
// width、height裁剪框大小
// img为Image实例
// left、top为裁剪框居于原图片的偏移量
function cutter(img, width, height, left, top, cb) {
const myCanvas = document.createElement("canvas");
const ctx = myCanvas.getContext("2d");
myCanvas.width = width; // 画布大小应为裁剪框大小
myCanvas.height = height; // 画布大小应为裁剪框大小
// 绘制图片时设置偏移量以及大小。参数6、7必须是0,可以让裁剪的内容刚好在画布中呈现。
ctx.drawImage(img, left, top, width, height, 0, 0, width, height);
// 将图片导出成blob
myCanvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
cb(url);
});
}
// width、height裁剪框大小
// img为Image实例
// left、top为裁剪框居于原图片的偏移量
function cutter(img, width, height, left, top, cb) {
const myCanvas = document.createElement("canvas");
const ctx = myCanvas.getContext("2d");
myCanvas.width = width; // 画布大小应为裁剪框大小
myCanvas.height = height; // 画布大小应为裁剪框大小
// 绘制图片时设置偏移量以及大小。参数6、7必须是0,可以让裁剪的内容刚好在画布中呈现。
ctx.drawImage(img, left, top, width, height, 0, 0, width, height);
// 将图片导出成blob
myCanvas.toBlob((blob) => {
const url = URL.createObjectURL(blob);
cb(url);
});
}
实现源代码
ts
// 点击确定
const handleConfrim = () => {
nextTick(() => {
// 完整图片的DOM
const imgDOM = preImgDOM.value;
if (imgUrl && imgDOM) {
const { naturalHeight, clientHeight } = imgDOM;
// 裁剪框信息
const { width, height, left, top } = cutterOffset.value;
// 用canvans实现裁剪
const canvas = document.createElement("canvas");
// 画布大小就是裁剪框大小
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
if (ctx) {
// 计算出原图片和当前图片的缩放比例
// 这一步非常关键,因为在渲染图片时,canvas是直接将图片原尺寸渲染上去了
// 会导致我们设置的偏移量和大小会有巨大的误差(因为我们是在被缩放过的图片调整裁剪框的位置大小)
// 所以我们需要计算出缩放的比例
const rate = +(clientHeight / naturalHeight).toFixed(2);
// 根据偏移量和尺寸渲染对应位置的图片
// 前面五个参数是渲染的图片、原图片的x坐标、原图片的y坐标、裁剪尺寸
// 后面四个参数是canvas画布的输出位置,将图片渲染在(0,0)的位置,大小为width, height
ctx.drawImage(
imgDOM,
left / rate,
top / rate,
width / rate,
height / rate,
0,
0,
width,
height
);
// 将图片导出
canvas.toBlob((blob) => {
if (blob && imgFileInfo) {
emit(
"cutDown",
new File([blob], imgFileInfo.name, {
type: imgFileInfo.type,
lastModified: imgFileInfo.lastModified,
})
);
} else {
emit("cutDown", null);
}
// 关闭模态框
handleCloseBtn();
});
} else {
props.otherError("画布上下文无法获取!");
// 关闭模态框
handleCloseBtn();
}
} else {
// 请先上传图片!
props.otherError("请先上传图片!");
}
});
};
// 点击确定
const handleConfrim = () => {
nextTick(() => {
// 完整图片的DOM
const imgDOM = preImgDOM.value;
if (imgUrl && imgDOM) {
const { naturalHeight, clientHeight } = imgDOM;
// 裁剪框信息
const { width, height, left, top } = cutterOffset.value;
// 用canvans实现裁剪
const canvas = document.createElement("canvas");
// 画布大小就是裁剪框大小
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
if (ctx) {
// 计算出原图片和当前图片的缩放比例
// 这一步非常关键,因为在渲染图片时,canvas是直接将图片原尺寸渲染上去了
// 会导致我们设置的偏移量和大小会有巨大的误差(因为我们是在被缩放过的图片调整裁剪框的位置大小)
// 所以我们需要计算出缩放的比例
const rate = +(clientHeight / naturalHeight).toFixed(2);
// 根据偏移量和尺寸渲染对应位置的图片
// 前面五个参数是渲染的图片、原图片的x坐标、原图片的y坐标、裁剪尺寸
// 后面四个参数是canvas画布的输出位置,将图片渲染在(0,0)的位置,大小为width, height
ctx.drawImage(
imgDOM,
left / rate,
top / rate,
width / rate,
height / rate,
0,
0,
width,
height
);
// 将图片导出
canvas.toBlob((blob) => {
if (blob && imgFileInfo) {
emit(
"cutDown",
new File([blob], imgFileInfo.name, {
type: imgFileInfo.type,
lastModified: imgFileInfo.lastModified,
})
);
} else {
emit("cutDown", null);
}
// 关闭模态框
handleCloseBtn();
});
} else {
props.otherError("画布上下文无法获取!");
// 关闭模态框
handleCloseBtn();
}
} else {
// 请先上传图片!
props.otherError("请先上传图片!");
}
});
};