本文主要使用了
element-ui
的图片上传组件获取图片,然后使用cropper.js
进行图片裁剪,在裁剪完以后进行格式转换,然后自行图片上传,文中的css使用了stylus
编译,如果不是请自行更改
1.插件安装
npm install cropperjs --save
element-ui
的安装就不予以赘述了,主要使用了element-ui的图片上传以及$message
2.组件编写
- 目录新建
在components
文件夹下新建upload
文件夹,然后在upload
文件夹下新建 cropperBox.vue
以及 upCover.vue
- 编写
cropperBox.vue
在cropperBox.vue
中写入以下内容,(finishCropImage
方法中的blob
的数据格式请自行选择以下两种,第一种是blob,第二种是普通文件方式。)
<template>
<div class="cropper-wrap">
<div class="cropper-alert-mask" :class="{ show: imgHasLoad }"></div>
<div class="cropper-alert" :class="{ show: imgHasLoad }">
<div class="cropper">
<span class="layout-icon-wrap"><i class="el-icon-circle-close" @click="imgHasLoad=false"></i></span>
<div class="cropper-box">
<img ref="uploadPreview" style="width:100px;height:auto;">
</div>
<div class="cropper-res-wrap">
<div class="cropper-res" id="cropperRes">
<img style="width:100px;height:100px;">
</div>
</div>
</div>
<div class="cropper-btns-wrap">
<el-progress
:text-inside="true"
:stroke-width="30"
:percentage="uploadProgress">
</el-progress>
<button
type="button"
class="cropper-btn"
@click="finishCropImage"
:disabled="btnTips.disable"
:class="{'btn-bg': uploading}">
{{ btnTips.value }}
</button>
</div>
</div>
</div>
</template>
<script>
import Cropper from 'cropperjs'
export default {
name: 'cropper-box',
props: {
options: {
default: {
aspectRatio: 1 / 1,
preview: '#cropperRes',
zoomOnWheel: false,
minCropBoxWidth: 50
}
},
uploadProgress: {
default: 0
}
},
data () {
return {
cropper: null,
imgHasLoad: false,
cropperHasInit: false,
uploading: false,
rawFile: null
}
},
watch: {
imgHasLoad (val) {
if (!val) {
this.uploading = false
}
}
},
computed: {
btnTips () {
if (this.uploading) {
return {
value: '正在上传,请稍等',
disable: true
}
}
return {
value: '裁剪完成,立即上传',
disable: false
}
}
},
methods: {
show () {
this.imgHasLoad = true
},
close () {
this.imgHasLoad = false
},
loadCropper (rawFile) {
this.rawFile = rawFile
const URL = window.URL || window.webkitURL
const blobURL = URL.createObjectURL(rawFile)
var image = this.$refs.uploadPreview
if (!this.cropper) this.cropper = new Cropper(image, this.options)
this.cropper.reset().replace(blobURL)
},
// 完成裁剪,将文件进行格式转换,发送给父组件,请自行选择普通文件格式还是blob格式,格式转换方法已封装,请看下面的两个方法
finishCropImage () {
this.uploading = true
const croppedCanvas = this.cropper.getCroppedCanvas()
const croppedDataUrl = croppedCanvas.toDataURL(this.rawFile.type)
const blob = this.base64toFile(croppedDataUrl)
this.$emit('finishCropImage', blob)
},
// dataUrl 转 blob
dataURLtoBlob (dataurl) {
/* eslint-disable */
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1];
var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {type:mime});
/* eslint-enable */
},
//base64转为普通文件格式
base64toFile (dataurl, filename = '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
})
},
}
}
</script>
<style lang="stylus">
@import "cropperjs/dist/cropper.min.css"
.cropper-wrap
.cropper-alert-mask
position: fixed
top: 0
left: 0
right: 0
bottom: 0
z-index: 2000
background-color: rgba(#000000, .5)
visibility: hidden
height: 0
transition: all .3s ease
.cropper-alert-mask.show
visibility: visible
height: 100%
.cropper-alert
opacity: 0
transition: all .3s ease
visibility: hidden
padding: 10px
position: fixed
z-index: 2000
top: 50px
left: 50%
transform: translateX(-50%) scale(2)
background-color: #ffffff
border-radius: 5px
overflow: hidden
width: 100%
height: 100%
max-width: 600px
max-height: 530px
&.show
opacity: 1
visibility: visible
transform: translateX(-50%) scale(1)
.cropper
position: relative
max-width: 600px
max-height: 460px
height: 100%
padding: 10px
padding-right: 120px
@media (max-width: 1324px)
padding-top: 120px
padding-right: 10px
background-color: #f9fbfc
.layout-icon-wrap
position: absolute
cursor: pointer
right: 0px
top: 0px
font-size: 20px
.cropper-box
position: relative
width: 100%
height: 100%
background-color: #ffffff
.cropper-res-wrap
position: absolute
top: 50%
transform: translateY(-50%)
@media (max-width: 1324px)
top: 0
left: 50%
transform: translateX(-50%)
right: 0
width: 100px
height: auto
padding: 10px
background-color: #a8a8a8
box-sizing: content-box
.cropper-res
width: 100px
height: 100px
overflow: hidden
background-color: #ffffff
.cropper-btns-wrap
position: relative
margin-top: 20px
.cropper-btn
position: absolute
left: 0
top: 0
width: 100%
height: 30px
line-height: 1
background: #ffffff
border: 1px solid #e1e1e1
border-radius: 15px
color: #666666
cursor: pointer
.btn-bg
background: #FF000000
</style>
- 编写
upCover.vue
在upCover.vue
中写入以下内容,(finishCropImage方法中请自行补充图片上传调用接口的方法)
<template>
<div
class="cover-upload-wrap"
ref=coverOutWrap
:style="{
width: width ? (width + 'px') : '100%',
height: calcHeight + 'px',
maxWidth: maxWidth ? (maxWidth + 'px') : '100px',
maxHeight: maxHeight ? (maxHeight + 'px') : '100px'
}">
<el-upload
ref="upload"
class="cover-uploader"
:show-file-list="false"
:before-upload="beforeVipImageUpload"
:auto-upload="false"
:on-change="onFileChange"
:on-progress="onUploadProgress">
<div class="img-wrap">
<img :src="imageUrl" class="cover" v-if="imageUrl">
<div class="img-mask-default" :class="{'img-mask': imageUrl}">
<i class="el-icon-upload"></i>
<div>{{ tip }}</div>
</div>
</div>
</el-upload>
<cropperBox
ref="cropperBox"
:options="options"
:uploadProgress="uploadProgress"
@finishCropImage="finishCropImage">
</cropperBox>
</div>
</template>
<script>
import uploadImage from "../../mixins/uploadImage";
import cropperBox from './cropperBox'
import {upload} from "../../api/base";
export default {
name: 'up-cover',
mixins:[uploadImage],
components: {
cropperBox
},
props: {
defaultImg: String,
ratio: { // 裁剪结果宽高比
default: 1
},
width: [Number,String],
height: [Number,String],
WHRatio: { // 组件宽高比
default: 1
},
cropBoxResizable:Boolean, //是否可以修改裁剪框的尺寸
maxWidth: String,
maxHeight: String,
tip: {
default: '上传图片'
},
maxSize: { // 最大选择图片的大小,单位M
default: 3
}
},
data () {
return {
cropper: null,
newFile: null,
options: {
aspectRatio: 2.166,
preview: '#cropperRes',
zoomOnWheel: false,
cropBoxResizable:false,
minCropBoxWidth: 50,
viewMode:3
},
token: {},
uploadProgress: 0,
calcHeight: 0
}
},
created() {
this.options.aspectRatio = this.ratio;
this.options.cropBoxResizable = this.cropBoxResizable;
if (this.height) {
this.calcHeight = this.height
}
},
computed: {
imageUrl () {
return this.defaultImg
}
},
mounted() {
if (!this.calcHeight) {
if (this.width) {
this.calcHeight = this.width / this.WHRatio
} else {
this.calcHeight = this.$refs.coverOutWrap.offsetWidth / this.WHRatio
}
}
},
methods: {
//截取图片上传的事件,有图片的情况就打开裁剪框,并且将文件传入到裁剪框中
onFileChange (file, fileList) {
if (file.status === 'ready') {
this.$refs.cropperBox.show();
this.$refs.cropperBox.loadCropper(file.raw)
}
},
//文件裁剪,nerFile就是最终拿到的文件,自行调接口进行图片上传
finishCropImage (newFile) {
this.newFile = newFile;
//图片上传调接口请自行补充
},
//上传图片之前的大小检测,文件大小通过prop传递,可在组件使用自定义文件大小限制
beforeVipImageUpload(file){
const isLt3M = file.size / 1024 / 1024 < this.maxSize;
if (!isLt3M) {
this.$message.error('上传图片大小不可大于' + this.maxSize + 'M');
}
return isLt3M;
let uploadFile = new window.File([this.newFile], file.name, { type: this.newFile.type });
uploadFile.uid = this.newFile.uid;
return Promise.resolve(uploadFile)
},
//文件上传进度
onUploadProgress (event, file, fileList) {
this.uploadProgress = parseInt(event.percent) - 1
}
}
}
</script>
<style lang="stylus">
.cover-upload-wrap
position: relative
width: 100%
max-width: 300px
height: 150px
border-radius: 5px
.cover-uploader
width: 100%
height: 100%
.el-upload
width: 100%
height: 100%
overflow: hidden
border-radius: 5px
cursor: pointer
border: 1px solid #dddddd
.img-wrap
position: relative
width: 100%
height: 100%
&:hover
.img-mask-default
opacity: 1
background-color: rgba(0, 0, 0, 0.5)
color: #ffffff
.cover
position: relative
width: 100%
height: 100%
border-radius: 5px
.img-mask-default
position: absolute
left: 0
top: 0
width: 100%
height: 100%
padding-left: 10px
padding-right: 10px
background-color: #ffffff
color: #555555
display: flex
flex-direction: column
justify-content: center
font-size: 12px
transition: all .2s linear
.el-icon-upload
font-size: 18px
.img-mask
opacity: 0
</style>
使用方法
- 引入插件
import UP from '@/components/upload/upCover'
export default {
components:{UP},
}
- 在页面中使用
pictureUrl
就是图片上传成功以后显示的回调地址,请自行填写,maxSize可以限定上传图片时候的尺寸大小,width和height限定组件大小,
<UP class="upload-cover"
:default-img="pictureUrl?pictureUrl:''"
ratio="2.166"
:cropBoxResizable="true"
width="156"
height="72"
tip="上传等级图标"
maxSize="3"
@uploadSuccess="uploadSuccess">
</UP>
在uploadSuccess
方法中可以拿到图片上传成功以后的回调,具体操作自行补充啦
uploadSuccess(res){
//this.pictureUrl=res.data
},
更多操作
javascript操作
this.myCropper.getCroppedCanvas().toDataURL('image/jpeg') //拿到裁剪后的base64的图片
this.myCropper.getCropBoxData(); //获取裁剪框数据
this.myCropper.setCropBoxData(); //设置裁剪框数据
this.myCropper.getCanvasData(); //获取图片数据
this.myCropper.setCanvasData(); //设置图片数据
配置对象
-
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)
容器、图片、裁剪框的 最小宽高 括号内为默认值 注意 裁剪框的最小高宽是相对与页面而言的 并非相对图片
方法
-
crop()
手动显示裁剪框 -
reset()
重置图片和裁剪框为初始状态 -
replace(url[, hasSameSize])
替换图片路径并且重建裁剪框-
url
新路径 -
hasSameSize
默认值false 设置为true表示新老图片尺寸一样 只需要更换路径无需重建裁剪框
-
-
enable()
解冻 裁剪框 -
disable()
冻结 裁剪框 -
destroy()
摧毁裁剪框并且移除cropper实例 -
move(offsetX[, offsetY])
移动图片指定距离 一个参数代表横纵向移动距离一样 -
moveTo(x[, y])
移动图片到一个指定的点 一个参数代表横纵向移动距离一样 -
zoom(ratio)
缩放 ratio大于零是放大 小于零缩小 -
zoomTo(ratio[, pivot])
缩放并设置中心点的位置 -
rotate(degree)
旋转 类似css -
rotateTo(degree)
旋转到绝对角度 -
scale(scaleX[, scaleY])、scaleX(scaleX)、scaleY(scaleY)
缩放 一个参数代表横纵向缩放值一样 -
getData([rounded])
返回裁剪区域基于原图片!原尺寸!的位置和尺寸 rounded默认为false 表示是否显示四舍五入后的数据 有了这些数据可以直接在原图上进行裁剪显示 -
setData(data)
改变裁剪区域基于原图的位置和尺寸 仅当viewMode 不为0时有效 -
getContainerData()、getImageData()、getCanvasData()、setCanvasData(data)、getCropBoxData()、setCropBoxData(data)
容器、图片容器(画布)、图片、裁剪区域相对容器的数据设置和获取 -
getCroppedCanvas([options])
得到被裁剪图片的一个canvas对象 options设置这个canvas的一些数据-
width、height、minWidth、minHeight、maxWidth、maxHeight、fillColor、imageSmoothingEnabled
(图片是否是光滑的 默认true)、imageSmoothingQuality(图片的质量 默认low 还有medium、high)
-
-
setAspectRatio(aspectRatio)
改变裁剪区域的宽高比 -
setDragMode([mode])
设置拖拽图片模式
事件
-
ready
渲染前(图片已经被加载、cropper实例已经准备完毕)的准备工作事件 -
cropstart、cropmove、cropend、crop
开始画裁剪框(或画布)、画裁剪框(或画布)的中途、裁剪框(或画布)画完、进行裁剪事件 event.detail.originalEvent、event.detail.action- 当autoCrop为true crop事件会在ready之前触发
-
zoom
裁剪框缩放事件
本文参考于https://www.cnblogs.com/eightFlying/p/cropper-demo.html