cropperjs地址:https://github.com/fengyuanchen/cropperjs
1.项目引入cropperjs(注:只演示vue3项目的引入步骤)
项目中安装cropperjs
npm install cropper --save
引入js和css
<script setup name="cropperDialog">
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
</script>
<div class="cropper-warp">
<div class="cropper-warp-left">
<img :src="state.cropperImg" class="cropper-warp-left-img" />
</div>
<div class="cropper-warp-right">
<div class="cropper-warp-right-title">预览</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="state.cropperImgBase64" class="cropper-warp-right-value-img" />
</div>
<div class="cropper-warp-right-label">100 x 100</div>
</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="state.cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
cropper-warp-left-img 的img标签为cropper的主裁剪区域,cropper-warp-right-value-img为cropperjs裁剪的实时预览区域,因为cropperjs裁剪后会吐出一个图片的base64字符串,可以实现动态预览,实际dom渲染效果如下图:
接下来是js部分
// 初始化cropperjs
const initCropper = () => {
const letImg = document.querySelector('.cropper-warp-left-img');
if (!state.cropper) {
// cropper对象不存在时再创建,有则利用上次已存在的对象
(state.cropper) = new Cropper(letImg, {
/*
* viewMode 视图控制
- 0 无限制
- 1 限制裁剪框不能超出图片的范围
- 2 限制裁剪框不能超出图片的范围 且图片填充模式为 cover 最长边填充
- 3 限制裁剪框不能超出图片的范围 且图片填充模式为 contain 最短边填充
* */
viewMode: 1,
/*设置图片是否可以拖拽功能
* dragMode 拖拽图片模式
- crop 形成新的裁剪框
- move 图片可移动
- none 什么也没有
* */
dragMode: 'none',
// 是否显示图片后面的网格背景,一般默认为true
background: true,
// 裁剪框宽高比的初始值
initialAspectRatio: 1,
// 设置裁剪框为固定的宽高比
aspectRatio: 1,
// 进行图片预览的效果
preview: '.before',
// 设置裁剪区域占图片的大小 值为 0-1 默认 0.8 表示 80%的区域
autoCropArea: 0.6,
// 设置图片是否可以进行收缩功能
zoomOnWheel: false,
// 是否显示 + 箭头
center: true,
crop: () => {
state.cropperImgBase64 = (state.cropper).getCroppedCanvas().toDataURL('image/jpeg');
},
});
}
}
cropperjs需要执行初始化函数后才能正常使用
const letImg = document.querySelector('.cropper-warp-left-img');
用于获取cropperjs的容器,也可指定id,这里是使用class属性
if (!state.cropper) {...}
这步是判断当前cropper是否已被渲染,因为我是封装成组件进行操作,防止重复初始化cropper导致浪费,初始化之前先判断一下,接下来,进行初始化cropper操作
(state.cropper) = new Cropper(letImg, {...})
配置参数列表如下:
属性名 | 说明 |
---|---|
viewMode | 视图控制0 无限制1 限制裁剪框不能超出图片的范围2 限制裁剪框不能超出图片的范围 且图片填充模式为 cover 最长边填充3 限制裁剪框不能超出图片的范围 且图片填充模式为 contain 最短边填充 |
dragMode | 拖拽图片模式 |
crop | 形成新的裁剪框 |
move | 图片可移动 |
none | 什么也没有 |
initialAspectRatio | 裁剪框宽高比的初始值 默认与图片宽高比相同 只有在aspectRatio没有设置的情况下可用 |
aspectRatio | 设置裁剪框为固定的宽高比 |
data | 之前存储的裁剪后的数据 在初始化时会自动设置 只有在autoCrop设置为true时可用 |
preview | 预览 设置一个区域容器预览裁剪后的结果Element, Array (elements), NodeList or String (selector) |
responsive | 在窗口尺寸调整后 进行响应式的重渲染 默认true |
restore | 在窗口尺寸调整后 恢复被裁剪的区域 默认true |
checkCrossOrigin | 检查图片是否跨域 默认true 如果是 会在被复制的图片元素上加上属性crossOrigin 并且在src上加上一个时间戳 避免重加载图片时因为浏览器缓存而加载错误 |
checkOrientation | 检查图片的方向信息(仅JPEG图片有)默认true 在旋转图片时会对图片方向值做一些处理 以解决IOS设备上的一些问题 |
modal | 是否显示图片和裁剪框之间的黑色蒙版 默认true |
guides | 是否显示裁剪框的虚线 默认true |
center | 是否显示裁剪框中间的 ‘+’ 指示器 默认true |
highlight | 是否显示裁剪框上面的白色蒙版 (很淡)默认true |
background | 是否在容器内显示网格状的背景 默认true |
autoCrop | 允许初始化时自动的裁剪图片 配合 data 使用 默认true |
autoCropArea | 设置裁剪区域占图片的大小 值为 0-1 默认 0.8 表示 80%的区域 |
movable | 是否可以移动图片 默认true |
rotatable | 是否可以旋转图片 默认true |
scalable | 是否可以缩放图片(可以改变长宽) 默认true |
zoomable | 是否可以缩放图片(改变焦距) 默认true |
zoomOnTouch | 是否可以通过拖拽触摸缩放图片 默认true |
zoomOnWheel | 是否可以通过鼠标滚轮缩放图片 默认true |
wheelZoomRatio 设置鼠标滚轮缩放的灵敏度 默认 0.1 | |
cropBoxMovable | 是否可以拖拽裁剪框 默认true |
cropBoxResizable | 是否可以改变裁剪框的尺寸 默认true |
toggleDragModeOnDblclick | 是否可以通过双击切换拖拽图片模式(move和crop)默认true 当拖拽图片模式为none时不可切换 该设置必须浏览器支持双击事件 |
minContainerWidth(200)、minContainerHeight(100)、minCanvasWidth(0)、minCanvasHeight(0)、minCropBoxWidth(0)、minCropBoxHeight(0) | 容器、图片、裁剪框的 最小宽高 括号内为默认值 注意 裁剪框的最小高宽是相对与页面而言的 并非相对图片 |
裁剪完成之后,会讲图片转成一个base64字符串
state.cropper.getCroppedCanvas({
imageSmoothingQuality: 'high'
}).toDataURL('image/jpeg'); // 设置图片格式
可以指定图片质量和图片格式,拿到base64字符串后,可以提交给后端进行保存(base64直接保存,或者转成file对象进行保存,我这里采用的第二种方式)
base64转file
感谢:https://www.jianshu.com/p/19fbe6bedf8e 提供的方法
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.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 File([u8arr], filename, {type:mime});
}
文件转成file对象后可以使用axios进行上传操作了
axios的headers设置成{'Content-Type': 'multipart/form-data'},采用post方式提交FormData,提交如下参数
let param = new FormData();
param.append('fileType', fileType)
param.append('file', base64toFile(base64Str, fileName))
// 进行提交相关操作
后台采用MultipartFile进行接收,代码如下:
**
* 上传文件
*
* @param file 文件
* @return {@link CommonResult}
*/
@PostMapping(value = "/uploadFile",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public CommonResult uploadFile(@RequestParam("file") MultipartFile file) {
QiniuResultDto result = uploadService.uploadFile(file);
if (result!=null) {
return CommonResult.success(result, "上传成功");
}
return CommonResult.failed("上传失败");
}
因为我的后台是直接上传到七牛云上,QiniuResultDto是封装的七牛云上传结果
注:@RequestParam("file")中的file名称要和前端js中formData的file的key对应**
下面贴一下我的cropper组件吧,使用的elementPlus框架,前端功底不太行,可能代码比较乱,仅供参考:
<template>
<div>
<el-dialog title="更换头像" v-model="state.isShowDialog" width="769px">
<div class="cropper-warp">
<div class="cropper-warp-left">
<img :src="state.cropperImg" class="cropper-warp-left-img" />
</div>
<div class="cropper-warp-right">
<div class="cropper-warp-right-title">预览</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="state.cropperImgBase64" class="cropper-warp-right-value-img" />
</div>
<div class="cropper-warp-right-label">100 x 100</div>
</div>
<div class="cropper-warp-right-item">
<div class="cropper-warp-right-value">
<img :src="state.cropperImgBase64" class="cropper-warp-right-value-img cropper-size" />
</div>
<div class="cropper-warp-right-label">50 x 50</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default">取 消</el-button>
<el-button type="primary" @click="onSubmit" size="default">更 换</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup name="cropperDialog">
import Cropper from 'cropperjs';
import 'cropperjs/dist/cropper.css';
const emit = defineEmits(['cropperDone']);
const state = reactive({
isShowDialog: false,
cropperImg: '',
cropperImgBase64: '',
cropper: null,
});
// 打开弹窗
const openDialog = (imgs) => {
state.cropperImg = imgs;
state.isShowDialog = true;
nextTick(() => {
initCropper();
});
};
// 关闭弹窗
const closeDialog = () => {
state.isShowDialog = false;
};
// 取消
const onCancel = () => {
closeDialog();
};
// 更换
const onSubmit = () => {
state.cropperImgBase64 = getCropperedImg();
// 完成后把参数回传给父组件
emit('cropperDone', state.cropperImgBase64)
closeDialog();
};
// 初始化cropperjs图片裁剪
const initCropper = () => {
const letImg = document.querySelector('.cropper-warp-left-img');
if (!state.cropper) {
// cropper对象不存在时再创建,有则利用上次已存在的对象
(state.cropper) = new Cropper(letImg, {
/*
* viewMode 视图控制
- 0 无限制
- 1 限制裁剪框不能超出图片的范围
- 2 限制裁剪框不能超出图片的范围 且图片填充模式为 cover 最长边填充
- 3 限制裁剪框不能超出图片的范围 且图片填充模式为 contain 最短边填充
* */
viewMode: 1,
/*设置图片是否可以拖拽功能
* dragMode 拖拽图片模式
- crop 形成新的裁剪框
- move 图片可移动
- none 什么也没有
* */
dragMode: 'none',
// 是否显示图片后面的网格背景,一般默认为true
background: true,
// 裁剪框宽高比的初始值
initialAspectRatio: 1,
// 设置裁剪框为固定的宽高比
aspectRatio: 1,
// 进行图片预览的效果
preview: '.before',
// 设置裁剪区域占图片的大小 值为 0-1 默认 0.8 表示 80%的区域
autoCropArea: 0.6,
// 设置图片是否可以进行收缩功能
zoomOnWheel: false,
// 是否显示 + 箭头
center: true,
crop: () => {
state.cropperImgBase64 = (state.cropper).getCroppedCanvas().toDataURL('image/jpeg');
},
});
}
}
// 获取裁剪后的文件
const getCropperedImg = () => {
// 拿到裁剪后的图片
return state.cropper.getCroppedCanvas({
imageSmoothingQuality: 'high'
}).toDataURL('image/jpeg'); // 设置图片格式
}
onMounted(() => {
});
// 暴露变量
defineExpose({
openDialog,
});
</script>
<style scoped lang="scss">
.cropper-warp {
display: flex;
.cropper-warp-left {
position: relative;
display: inline-block;
height: 350px;
flex: 1;
border: var(--el-border-base);
background: var(--el-color-white);
overflow: hidden;
background-repeat: no-repeat;
cursor: move;
border-radius: var(--el-border-radius-base);
.cropper-warp-left-img {
width: 100%;
height: 100%;
}
}
.cropper-warp-right {
width: 150px;
height: 350px;
.cropper-warp-right-title {
text-align: center;
height: 20px;
line-height: 20px;
}
.cropper-warp-right-item {
margin: 15px 0;
.cropper-warp-right-value {
display: flex;
.cropper-warp-right-value-img {
width: 100px;
height: 100px;
border-radius: var(--el-border-radius-circle);
margin: auto;
}
.cropper-size {
width: 50px;
height: 50px;
}
}
.cropper-warp-right-label {
text-align: center;
font-size: 12px;
color: var(--el-text-color-primary);
height: 30px;
line-height: 30px;
}
}
}
}
</style>