cropperjs的主要功能是图片裁剪,是一款前端常用的的图片裁剪工具,可根据相关api配置裁剪出符合自己业务需要的图片,具体使用如下:
- npm 引用
npm i cropperjs
- 1
- 业务中引入
import Cropper from 'cropperjs';
- 1
- 文件中单独引入方式
<link href="/path/to/cropper.css" rel="stylesheet"> | |
<script src="/path/to/cropper.js"></script> |
- 1
- 2
- 初始化图片裁剪
new Cropper(element, options)
- 1
参数:
element: 需要裁剪的图片元素,一般指本地获取到的img展示标签 | |
options: { | |
aspectRatio: 1 / 1, // 裁剪框纵横尺寸比例 | |
autoCropArea: 1, // 它应该是一个介于 0 和 1 之间的数字。定义自动裁剪区域大小(百分比), 默认80% | |
viewMode: 1, // 视图模式 | |
dragMode: "move", // 图片可移动 拖拽模式 | |
cropBoxMovable:false, //是否可以拖拽裁剪框 | |
preview:ele,// Dom元素,该元素的预览尺寸样式尽量和aspectRatio尺寸比例保持一致 | |
responsive: true, // 调整窗口大小时重新渲染裁剪器 | |
modal: true,// 在图像上方和裁剪框下方显示黑色模态 | |
rotatable: true,// 启用以旋转图像 | |
scalable: true, // 启用以缩放图像 | |
zoomable: true,// 启用以缩放图像 | |
zoomOnTouch: true, // 启用通过拖动触摸来缩放图像 | |
zoomOnWheel: true, //鼠标滚轮缩放 | |
cropBoxMovable: false, // 裁剪框可移动 | |
cropBoxResizable: false, // 裁剪框大小可调整 | |
resizable: false, // 是否允许改变裁剪框大小 | |
ready: Function, // 裁剪实例准备完成回调,由于加载图片时有一个异步过程,所以大部分方法都应该在 ready 之后调用 | |
reset: Function, // reset() 重置 | |
clear: Function, // clear() 清除 | |
replace(url, hasSameSize): Function, // url 图片地址, hasSameSize:Boolean,如果新图像与旧图像大小相同,则不会重建裁剪器,只会更新所有相关图像的 URL。这可用于应用过滤器 | |
... | |
} |
配置项:
viewMode | |
type: | Number |
default: | 0 |
option: | 0:无限制 1: 限制裁剪框不超过画布的大小。 2: 限制最小画布大小以适合容器。如果画布和容器的比例不同,最小画布将被其中一个维度中的额外空间包围。 3: 限制最小画布大小以填充容器。如果画布和容器的比例不同,容器将无法在其中一个维度中容纳整个画布。定义裁剪器的视图模式。 |
如果将viewMode设置为0,裁剪框可以延伸到画布之外,而值为1、2或3将裁剪框限制为画布的大小。viewMode为2或3将额外将画布限制为容器。当画布和容器的比例相同时,2和3之间没有差异。
一. 首先通过input file拿到的本地展示路径有两种:
1.base64格式
2.url格式
base64获取方式: 通过FileReader实例完成后的onload事件获取
url方式:URL.createObjectURL(img) -浏览器可识别的URL图片路径
uploadAwatar (value){ | |
// 图片上传成功后创建 URL | |
const fileList = value.target.files; // input file文件 | |
let img = ele; // 展示的裁剪图 | |
if (fileList.length) { | |
// url 格式获取可识别的图片路径 | |
let imgUrl = URL.createObjectURL(fileList[0]); | |
img.src = imgUrl; | |
img.onload = function(){ | |
setCutImg (fileList[0].type); // 裁剪函数 | |
} | |
// 转base64 | |
/** | |
if (typeof FileReader === 'function') { | |
const reader = new FileReader(); | |
reader.readAsDataURL(fileList[0]); | |
reader.onload = (event) => { | |
var Base64Val = event.target.result; | |
img.src = Base64Val; | |
console.log( Base64Val) | |
} | |
} **/ | |
} | |
} |
二. 通过上面有了本地图片展示就满足了图片裁剪的条件,传入dom, 初始化图片裁剪
/** | |
* @param setCutImg 初始化裁剪函数 | |
* @param imgEle 本地获取到的图片展示元素 | |
*/ | |
setCutImg (type){ | |
let objOpt = {}; | |
let winW = cropperPhoto.data.winW - 20; // 手机端两侧10px间距 | |
// 正方形头像裁剪,图片竖图,窄图始终以最小的边居中填充裁剪框 | |
if (imgEle.naturalWidth > imgEle.naturalHeight) { // 横图 | |
objOpt = { | |
minCropBoxHeight: winW, | |
minCanvasHeight: winW, | |
minContainerHeight: winW, | |
} | |
} else { // 竖图 | |
objOpt = { | |
minCropBoxWidth: winW, | |
minCanvasWidth: winW, | |
minContainerWidth: winW, | |
} | |
} | |
cropper = new Cropper(imgEle, { | |
...objOpt, | |
aspectRatio: 1 / 1, | |
autoCropArea: 1, | |
viewMode: 1, | |
dragMode: "move", //图片可移动 | |
cropBoxMovable:false, //是否可以拖拽裁剪框 | |
zoomOnTouch: true, | |
scalable: true, | |
zoomable: true, | |
cropBoxMovable: false, // 裁剪框可移动 | |
cropBoxResizable: false, // 裁剪框大小可调整 | |
resizable: false, // 是否允许改变裁剪框大小 | |
ready(){ | |
let _this = this; | |
$('.upload-cut').off().on('click', '.cancel', ()=> { // 关闭时处理的逻辑... | |
_this.cropper && _this.cropper.destroy(); // 销毁实例 | |
}).on('click', '.sure', async ()=> { // 确认裁剪的逻辑 | |
let cas = _this.cropper.getCroppedCanvas({ | |
width: winW, // 根据需求配置最终裁完的图片宽 | |
height: winW, // 根据需求配置最终裁完的图片高 | |
maxWidth: 500, // 头像所以我这给的最大是500 | |
maxHeight: 500, | |
fillColor: '#fff', // 如果有空白背景色填充 | |
imageSmoothingEnabled: false, | |
}).toBlob(async(blob) => { | |
// blob 流提交 | |
var formData = new FormData(); | |
/* * | |
// 方法一 | |
let arr = new Array(blob); | |
console.log(arr, 'blob'); | |
let file = new File(arr, 'file', { type: blob.type,}); | |
console.log(file, 'file'); | |
formData.append('avatarFile', file); */ | |
blob.lastModifiedDate = new Date(); | |
blob.name = 'file'; | |
blob.filename = 'file'; | |
formData.append('avatarFile', blob); // 后端以avatarFile取到blob数据流 | |
console.log(formData, 'formData'); // formdata类型数据提交 | |
commitData.call(this, formData) // 数据提交 | |
}) | |
/** | |
// 方法二 | |
// 拿到base64 转成 file流提交 | |
let base64url = cas.toDataURL(type); | |
let fileData = dataURLtoFile(base64url); | |
console.log(fileData, 'fileData'); | |
var formData = new FormData(); // formdata类型数据提交 | |
formData.append('avatarFile', fileData); | |
commitData.call(this, formData) // 数据提交 | |
**/ | |
/** | |
// 方法三 | |
// base64 转blob流转file流提交 | |
let base64url = cas.toDataURL(type); | |
// 调用如下 | |
let blob = dataURLtoBlob(base64url); | |
let fileData = blobToFile(blob, "fileName"); | |
// 上传file就可以了 | |
console.log(fileData, 'fileData'); | |
var formData = new FormData(); // formdata类型数据提交 | |
formData.append('avatarFile', fileData); | |
commitData.call(this, formData) // 数据提交 | |
**/ | |
}) | |
} | |
}); | |
} | |
function commitData(formData){ | |
let _this = this; | |
$.ajax({ | |
url: path, | |
method: "POST", | |
data: formData, | |
success: function(res){ | |
.... | |
_this.cropper && _this.cropper.destroy(); // 裁剪完成释放空间销毁实例 | |
}, | |
error: function(err){ | |
.... | |
_this.cropper && _this.cropper.destroy(); // 裁剪完成释放空间销毁实例 | |
} | |
}) | |
} | |
function dataURLtoFile (dataurl, filename = 'file') { // base64转file流 | |
let arr = dataurl.split(',') | |
let mime = arr[0].match(/:(.*?);/)[1] // 正则匹配文件类型 | |
let suffix = mime.split('/')[1] | |
let bstr = atob(arr[1]) | |
let n = bstr.length | |
let u8arr = new Uint8Array(n) | |
while (n--) { | |
u8arr[n] = bstr.charCodeAt(n) | |
} | |
return new File([u8arr], `${filename}.${suffix}`, {type: mime}) | |
}, | |
function dataURLtoBlob(toDataURL) { // base64转blob | |
var arr = toDataURL.split(","), | |
mime = arr[0].match(/:(.*?);/)[1], | |
bstr = atob(arr[1]), | |
n = bstr.length, | |
u8arr = new Uint8Array(n); | |
while (n--) { | |
u8arr[n] = bstr.charCodeAt(n); | |
} | |
return new Blob([u8arr], { type: mime }); | |
}, | |
function blobToFile(Blob, fileName) { // blob 模拟file流 | |
Blob.lastModifiedDate = new Date(); // 或者Date.now(), 文件最近一次的修改时间 | |
Blob.name = fileName; // 文件名 | |
return Blob; | |
} |
new File(data, fileName, options);
第一个参数是个数组,数组项可以是 ArrayBuffer, String 等等,第二个参数fileName指文件名称,第三个options是配置项,支持 type 和 lastModified 属性,type 可以传入 text/plain, text/html 等,lastModified 默认为 Date.now()
实例上的属性 | 说明 |
lastModified | 返回文件最后的修改时间 ,是个时间戳 |
lastModifiedDate | 返回文件最后的修改时间,一个 Date 对象name文件名称 |
size | 文件大小 |
webkitRelativePath | 文件的本地路径或者 |
type | 文件的MIME 类型 |
const file = new File(["foo"], "foo.txt", { | |
type: "text/plain", | |
lastModified: Date.now() | |
}) | |
file.name // foo.txt |
-
FileList
FileList 一个 File 的数组,它只有一个方法:
files.item(i) // == files[i]
- 1
new Blob(data, options);
与File构造函数类似,第一个参数是个数组,数组项可以是 ArrayBuffer, String 等等,第二个是配置项,最常用的就是 type 属性,可以传入 text/plain, text/html 等
属性和方法 | 说明 |
sizeBlob | 对象中所包含数据的大小(字节) |
type | 一个字符串,表明该 Blob 对象所包含数据的 MIME 类型 |
slice(start, end) | 返回一个新的 Blob对象,包含了源 Blob 对象中指定范围内的数据。和字符串的 slice 方法类似 |
stream() | 返回一个能读取 blob 内容的 ReadableStream |
text() | 返回一个 promise 且包含 blob 所有内容的UTF-8格式的字符串 |
arrayBuffer() | 返回一个promise且包含blob所有内容的二进制格式的 ArrayBuffer |
const blob = new Blob(['hello world'], { type: 'text/plain' }) | |
blob.text().then(console.log) // 'hello world' |
- 1
- 2
FileReader
FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,可以读取 Blob 和 File 的数据。
一个简单的使用例子,初始化后,监听 load 事件,然后调用读取方法。
const reader = new FileReader(); | |
reader.onload = function(evt) { | |
console.log(evt.target.result); | |
}; | |
reader.readAsText(file) |
事件 | 说明 |
onabort | 读取操作被中断事件 |
onerror | 读取操作发生错误的事件 |
onload | 读取操作完成的事件 |
onloadstart | 该事件在读取操作开始时触发 |
onloadend | 该事件在读取操作结束时(要么成功,要么失败)触发 |
onprogress | 取 Blob 时触发 |
方法 | 说明 |
abort | 中止读取readAsArrayBuffer开始读取数据,读取完后 result 是 ArrayBuffer 对象 |
readAsBinaryString | 开始读取数据,读取完后 result 是二进制数据 |
readAsData | URL开始读取数据,读取完后 result 是 Base64 字符串 |
readAsText | 开始读取数据,读取完后 result 是字符串 |
备注:还未亲自测试,但应该可以,值得参考