工作中遇到这样一个需求:
-
浏览器上传一张图片
-
服务器返回一张处理过的图片(url)和附加坐标信息
-
用户点击,浏览器识别对应区域,裁剪这张返回的图片
-
浏览器上传裁剪后的图片区域
前两步不必多说,裁剪图片,我们需要用到 canvas
一、获取图片
从 img 标签获取
这里的图片我们已经根据返回的 url 放在 img 标签中了,这样就很方便了
<img src="" alt="" id="image" />
const image = document.querySelector('#image');
从 input[type=file] 获取
抛开这次,通常我们需要获取的是用户上传的图片,我们需要用到 FileReader,在用户选择本地文件时读取这些文件的信息。
document.querySelector('input[type=file]').addEventListener('change', () => {
const file = this.files[0]; // File API 支持用户多选,this.files 得到的是一个 FileList 对象
const reader = new FileReader();
reader.onload = (e) => {
const image = new Image();
image.src = e.target.result; // 获取 DataURL
image.onload = () {
// 这里可以获取图片的信息,或做一些限制
// const width = image.width;
// const height = image.height;
}
}
reader.readAsDataURL(file);
})
二、canvas 裁剪图片
首先我们需要一个绘图环境
<canvas id="canvas" style="display: none"></canvas>
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
接着用 drawImage() API 做切片(Slicing),对照着上面链接的 API 文档填入参数( 注意 MDN 上参数的说明和实际参数的顺序是不一致的)
const width = xmax - xmin; // 裁剪区域的宽,xmax...为裁剪区域的四个坐标
const height = ymax - ymin;
ctx.drawImage(
image, // 第一步获取的图片对象
xmin, // image 上裁剪的 x 轴起点
ymin,
width, // image 上裁剪区域的宽度
height,
0, // canvas 上 x 轴开始绘制的起点
0,
width, // canvas 上绘制图像的宽度
height
);
三、读取 canvas 数据
我们需要了解(一些编码转换...着实麻烦)
// 获取包含图片信息的 data URI
let data = canvas.toDataURL();
// data URIs 的语法结构为:data:[<mediatype>][;base64],<data> 我们只需要 <data> 部分
data = data.spilt(',')[1];
// 解码base-64编码的数据
data = window.atob(data);
// 无符号整型数组
const u = new Uint8Array(data.length);
// 转化为 Unicode 值
for (var i = 0; i < data.length; i++) {
u[i] = data.charCodeAt(i);
}
// 构建 Blob 类文件对象
const blob = new Blob([u], { type: "image/png" })
被污染的 canvas
等等,这里你大概率会遇到一个奇怪的报错:被污染的 canvas
什么是“被污染”的 canvas?
尽管不通过 CORS 就可以在画布中使用图片,但是这会污染画布。一旦画布被污染,你就无法读取其数据。例如,你不能再使用画布的 toBlob(), toDataURL() 或 getImageData() 方法,调用它们会抛出安全错误。
这种机制可以避免未经许可拉取远程网站信息而导致的用户隐私泄露
我们需要给 image 设置 crossorigin 属性为 anonymous,来允许画布使用跨域的 img 元素图像
-
如果从 img 标签获取的图片
<img crossorigin="anonymous" src="" alt="">
-
如果从 input[type=file] 获取
const image = new Image(); image.crossOrign = 'anonymous'; image.src = e.target.result;
四、发送 formData
我们需要将得到的 blob 数据 append 到 formData 对象中,通过 ajax 发送(如果用 jquery,需要将 processData 和 contentType 设为 false,为了禁止 jquery 对 data 对象的默认 application/x-www-form-urlencoded 转换)
const formData = new FormData();
formData.append('file', blob);
formData.append(key, value); // 如果有附加的数据
$.ajax({
url: '/url',
type: 'POST',
data: formData,
processData: false,
contentType: false
});
由于传送的是 blob 数据,我们无法在开发者工具的 network 中 preview 图片,但可以在 canvas 中显示裁剪出来的图片
网友评论