在一些需求中,需要上传图片,而且有时上传的图片是要裁剪成一定的比例才能上传,这时我们就可以用cropperjs实现这个功能。
cropperjs可以进行非常灵活的配置来实现图片的裁剪,可以生成一个裁剪之后的canvas对象,还可以利用toDataURL方法生成Base64格式的图片。如果不使用canvas的方式,利用该工具丰富的api可以拿到裁剪区域相对于原图的各项数据,使用这些数据进行css绝对定位即可展示裁剪后的图,该方式可以保证图片不失真和完整。支持在移动端或者pc端(IE9+)使用。
废话不多说,现在开始演示在vue中如何使用:
一、引入:
npm install cropperjs
二、使用:
主要和vant结合使用
创建一个组件cutImg.vue
cutImg.vue
template部分:
<template>
<div class="cut_img">
<div class="top_panel">
<span @click="$emit('cancel')">取消</span>
<span @click="save">完成</span>
</div>
<!-- 裁剪的主体部分 -->
<div class="container">
<div class="img-container">
<img :src="img" ref="image" />
</div>
</div>
<div class="bottom_panel">
<div @click="rotate">
<VantIcon :name="require('@imgs/icon/rotate.png')" />
<span>旋转</span>
</div>
<div @click="resetRotate">
<VantIcon :name="require('@imgs/icon/reset.png')" />
<span>还原</span>
</div>
</div>
</div>
</template>
主要的效果是:
script部分:
<script>
import Cropper from "cropperjs";
import "cropperjs/dist/cropper.css";
import { base64ToFile } from "@tools/handle.js";
import { Icon as VantIcon } from "vant";
export default {
name: "CutImg",
props: {
width: {
type: Number,
default: window.innerWidth * 0.8,
},
height: {
type: Number,
default: window.innerWidth * 0.8 * 0.5625,
},
img: String, // 从本地上传的图片
ratio: { // 裁剪的比例
type: Number,
default: 1,
},
},
components: { VantIcon },
data() {
return {
myCropper: null,
cropperInstance: null,
};
},
mounted() {
this.init();
},
methods: {
init() {
let vm = this;
// 实例化cropper
this.myCropper = new Cropper(this.$refs.image, {
aspectRatio: this.ratio, // 裁剪的比例
dragMode: "move", // 模式,可移动
background: false,
ready() {
vm.cropperInstance = this.cropper; // 裁剪下来的图片
},
});
},
// 以90度进行旋转
rotate() {
this.cropperInstance.rotate(-90);
},
// 还原
resetRotate() {
this.cropperInstance.reset();
},
save() {
// 获取裁剪下来图片的base64,其质量为medium
let base64 = this.myCropper.getCroppedCanvas({
imageSmoothingQuality: "medium",
}).toDataURL("image/jpeg");
let file = base64ToFile(base64,`${new Date().getTime().toString()}.jpg`);
this.$emit("ok", { base64, file });
},
},
};
到此为止,裁剪的组件已经搭建完成,现在就是在我们需要的地方进行使用
home.vue
<template>
<div class="home">
<Uploader
type="file"
v-model="img"
:max-count="1"
accept="image/*"
:after-read="afterRead"
:before-read="beforeRead"
:preview-options="preOptions"
>
<img class="photo" :src="require('@imgs/upload.png')" alt="" />
</Uploader>
<Popup
v-model="showCutImg"
position="bottom"
:style="{ height: '100%', background: '#000' }"
get-container="#cutImg_box"
>
<CutImg
v-if="showCutImg"
:ratio="1 / 1"
:img="imgWillCut"
@cancel="onCutCancel"
@ok="onCutOk"
/>
</Popup>
</div>
</template>
<script>
import { compressImg } from "@tools/handle.js";
import { Uploader, Popup, Toast } from "vant";
import CutImg from "@comp/CutImg";
export default {
name: "Home",
components: {
Uploader,
Popup,
CutImg,
},
data() {
return {
showCutImg: false,
imgWillCut: "",
img: []
};
},
computed: {
preOptions() {
return {
showIndex: false,
closeable: true,
};
},
},
methods: {
// 读取图片数据
afterRead(ctn) {
this.showCutImg = true;
console.log("读取图片数据", ctn);
this.imgWillCut = ctn.content;
},
// 读取图片数据之前,检测图片大小
beforeRead(file) {
// 大于 2m 小于 10M 则压缩, 大于 10M 则提示图片过大
return new Promise((resolve, reject) => {
if (file.size > 10485760) {
Toast(`图片大小不得超过10M,请重新选择图片`);
return reject(false);
}
// 小于5M直接生成
if (file.size < 2097152) return resolve(file);
// 大于5M则压缩
if (file.size >= 2097152) {
Toast.loading({
message: "图片加载中...",
forbidClick: true,
});
// 对图片进行压缩
compressImg(file)
.then((fs) => {
resolve(fs);
})
.catch((err) => {
console.log("压缩图片报错", err);
})
.finally(() => {
Toast.clear();
});
}
});
},
// 取消裁剪
onCutCancel() {
this.showCutImg = false;
},
// 裁剪成功
onCutOk(result) {
this.showCutImg = false;
this.img[0].file = result.file;
this.img[0].content = result.base64;
},
},
};
</script>
<style lang="scss">
.home {
min-height: 100vh;
font-size: 20px * $scale;
background-color: rgba(138, 0, 11, 0.7);
.van-uploader {
display: block;
width: 500px * $scale;
height: 500px * $scale;
margin: 0 auto;
}
.van-uploader__input-wrapper {
margin: 0 auto;
}
.van-uploader__preview {
margin: 0;
width: 100%;
}
.van-uploader__preview-image {
width: 100%;
height: 100%;
}
}
</style>
其中handle.js
const base64ToFile = (dataurl, fileName) => {
// global atob Uint8Array File
let arr = dataurl.split(',')
let imgType = arr[0].match(/:(.*?);/)[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, { type: imgType })
}
function fileToBase64(file) {
// 创建 FileReader 解析为 base64 格式
let fileReader = new FileReader()
fileReader.readAsDataURL(file)
return new Promise((resolve, reject) => {
// 解析成功
fileReader.onload = function () {
resolve(fileReader.result)
}
// 发生错误
fileReader.onerror = function (err) {
reject('error: 文件讀取發生錯誤', JSON.stringify(err))
}
})
}
// 压缩图片
const compressImg = (file) => {
return new Promise((resolve, reject) => {
fileToBase64(file)
.then(src => {
// 根据 src 渲染图像
let img = new Image()
img.src = src
// 等待图片加载完毕
img.onload = () => {
// 根据图片, 绘制 canvas
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
let maxWidth = 1024;
let scale = Math.min(1, maxWidth / Math.max(img.width, img.height));
canvas.width = img.width * scale
canvas.height = img.height * scale
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
// 进行压缩, 获取 base64
let base64 = canvas.toDataURL('image/jpeg', 0.5)
// 响应结果
let nfile = base64ToFile(base64, file.name)
resolve(nfile)
}
img.onerror = (err) => {
reject(err)
}
}).catch(err => {
reject(err)
})
})
}
export {
base64ToFile,
compressImg
};
最后的效果流程:
相应代码:代码