Vue3+elementPlus使用cropperjs实现头像上传

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渲染效果如下图:
image-1649399597180

接下来是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>

# Vue 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×