一、前言
最近做项目中遇到一个需求,需要对海报图片按照一定的比例进行裁剪并上传到oss。一开始这个需求思路有两个,使用canvas原生或者寻找现成的第三方库,对比了一番觉得canvas实现时间耗费较长,且秉承着不重复造轮子的原则(其实是菜)。
在进行技术调研后,决定使用vue-cropper插件来实现,预想会顺利,可结果恰恰相反!安装vue-cropper设置参数封装完组件后,发现裁剪框和裁剪的图片在h5页面上不能拖动,在web页面上可以,看源码里面也有兼容移动端的方法代码,在github上的issue里面也没发现有人遇到相同问题,难道就我遇到了?问度娘发现大家都可以,为什么我的就不行呢?后来又使用了vue-cropper-h5,也存在各种问题。其实,vue-cropper、vue-cropper-h5、vue-cropperjs等插件都是基于cropper.js实现的,最终决定使用cropper.js去实现。官方封装了很多参数、方法、事件,上手容易,文档阅读体验较好、而且便于扩展。下面就来详细介绍一个cropper.js的详细用法吧!
cropper.js可以对指定的图片进行裁剪,可以自己选择裁剪的交互方式,如大小、纵横比等,裁剪后可以生成一个包含裁剪图的canvas对象,借助canvas的toDataURL方法可以生成一张Base64格式的图片,再通过toBlob转换成blob类型文件,再通过new File(),转换成file文件上传,当然也可以直接上传裁剪后生成的base64。还有另外一种不使用canvas的方式,利用该工具丰富的api可以拿到裁剪区域相对于原图的各项数据,使用这些数据进行css绝对定位即可展示裁剪后的图,该方式可以保证图片不失真和完整。
二、项目环境
1.node
v16.0.0
2.vue
"vue": "^3.2.45"
3.vant
"vant": "^4.0.2"
4.cropper.js
"cropperjs": "^1.5.13"
三、使用
1.安装
yarn add cropperjs
2.引入,要导入样式,不然不会生效
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.min.css'
3.模板
<div class="v-simple-cropper">
<van-uploader
:after-read="afterRead"
>
<template #default>
<img v-if="imgUrl" class="upload-img" :src="imgUrl" alt="">
<div v-else class="upload-area" />
</template>
</van-uploader>
<div v-show="showlayer" class="v-cropper-layer">
<van-row class="layer-header" :gutter="36">
<van-col :span="8">
<van-button type="danger" plain block size="small" @click="cancelHandle">
取消
</van-button>
</van-col>
<van-col :span="8">
<van-button type="success" block size="small" @click="rotateHandle">
旋转
</van-button>
</van-col>
<van-col :span="8">
<van-button type="primary" block size="small" @click="confirmHandle">
裁剪并上传
</van-button>
</van-col>
</van-row>
<img ref="cropperImg">
</div>
</div>
</template>
4.js
<script setup lang="ts">
import Cropper from 'cropperjs'
import 'cropperjs/dist/cropper.min.css'
import { onMounted, ref, watch } from 'vue'
import { closeToast, showFailToast, showLoadingToast, showToast } from 'vant'
import { useRequest } from 'vue-request'
import { fileUpload } from '@/api/basic/index'
const props = defineProps({
url: {
type: String,
default: '',
},
width: {
type: Number,
},
height: {
type: Number,
},
scale: {
type: Number,
default: 0.8
}
})
const emit = defineEmits(['update:url'])
const cropperImg = ref<any>(null)
const cropper = ref<any>(null)
const showlayer = ref(false)
const imgUrl = ref(props.url)
watch(() => props.url, (val) => {
if (val)
imgUrl.value = val
})
function init() {
cropper.value = new Cropper(cropperImg.value, {
viewMode: 1,
// 根据所需图片宽高,计算比例
aspectRatio: props.width && props.height ? (Number(props.width) / Number(props.height)) : 1 / 0.618,
dragMode: 'move',
cropBoxResizable: false,
autoCropArea: 1,
})
}
// 选择图片
function afterRead(file) {
// 判断扩展名
const tmpCnt = file.file.name.lastIndexOf('.')
const exName = file.file.name.substring(tmpCnt + 1)
const names = ['jpg', 'jpeg', 'png']
if (!names.includes(exName)) {
showToast('不支持的格式!')
return
}
const URL = window.URL || window.webkitURL
const binaryData = []
binaryData.push(file.file)
cropper.value.replace(URL.createObjectURL(new Blob(binaryData)))
showlayer.value = true
}
// 旋转
function rotateHandle() {
cropper.value.rotate(90)
}
function cancelHandle() {
cropper.value.reset()
showlayer.value = false
}
// 上传方法
const { run: runUplaod } = useRequest(fileUpload, {
manual: true,
onSuccess: (res) => {
closeToast()
imgUrl.value = res.imgUrl
emit('update:url', res.imgUrl)
},
onError: (err) => {
closeToast()
showFailToast(err.message)
},
})
// 裁剪并上传
function confirmHandle() {
const cropBox = cropper.value.getCropBoxData();
const scale = props.scale || 1;
cropper.value.getCroppedCanvas({
width: cropBox.width * scale,
height: cropBox.height * scale,
})
// 把裁剪的图片转换成blob类型文件,在通过new File(),转换成file文件
.toBlob(async (blob) => {
// 重置file的name,以时间戳作为文件名
const timeString = new Date().getTime()
const imgFile = new File([blob], `${timeString}.jpg`, {
type: 'image/jepg',
})
showlayer.value = false
showLoadingToast({
message: '上传中...',
duration: 0,
forbidClick: true,
})
runUplaod(imgFile)
})
}
onMounted(() => {
init()
})
</script>
5.style
<style lang="less" scoped>
.v-simple-cropper {
text-align: center;
padding-top: 10px;
.upload-area {
width: 190px;
height: 120px;
background: url('@/assets/bg-upload-box.png');
background-size: 100% 100%;
}
.upload-img {
width: 190px;
height: 120px;
}
/deep/.van-uploader__upload {
margin: 0 !important;
}
.v-cropper-layer {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #fff;
z-index: 200;
.layer-header {
position: absolute;
bottom: 0;
left: 0;
z-index: 200;
width: 100%;
padding: 0 16px 12px;
box-sizing: border-box;
}
img {
position: inherit !important;
border-radius: inherit !important;
float: inherit !important;
}
}
}
</style>
在上面的代码中,我们使用了cropperjs组件来实现图片裁剪功能。在afterRead方法中,我们获取用户上传的图片,并将其转换为URL对象。在confirmHandle方法中,我们使用getCroppedCanvas方法获取裁剪后的canvas对象,并使用toBlob方法将其转换为Blob对象,最后将其转换为File对象并上传oss,返回一个oss url。
四、其他
1.预览功能
上面创建cropper的时候,我们可以在选项中添加了如下代码实现预览功能。
preview:[
document.querySelector('.previewBox'),
document.querySelector('.previewBoxRound')
]
preview就是用来设置我们需要实时预览的地方,但是设置完成之后要给上述的两个div添加一下样式,才可以正常显示。
.previewBox {
box-shadow: 0 0 5px #adadad;
width: 100px;
height: 100px;
margin-top: 30px;
/*这个超出设置为隐藏很重要,否则就会整个显示出来了*/
overflow: hidden;
}
2. 官方文档
(1)官网示例
https://fengyuanchen.github.io/cropper/
(2)官方github文档
https://github.com/fengyuanchen/cropperjs
(3)npm官方网站文章来源:https://www.toymoban.com/news/detail-789846.html
https://www.npmjs.com/package/cropper文章来源地址https://www.toymoban.com/news/detail-789846.html
到了这里,关于vue3+vant+cropper.js实现移动端图片裁剪功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!