1.vue3+element-plus+vue-cropper实现裁剪图片
element-UI官网 element-plus官网 vue-cropper vue3使用vue-cropper安装:npm install vue-cropper@next
2.vue-cropper插件:
< vue-cropper :img = " option.img" />
< script setup >
import { reactive} from "vue" ;
const option = reactive ( {
img : 'https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500'
} )
</ script>
3.效果图:
4.实现cropperUpload组件:
< template>
< div class = " uploadMian" >
< div class = " img-item" v-for = " (item, index) in fileList" :key = " index" >
< img :src = " item.src" />
< el-icon class = " uploader-close" @click = " delFn(index)" > < Close /> </ el-icon>
< div v-if = " item.isSuccess" class = " uploader-Check" > < el-icon > < Check /> </ el-icon> </ div>
< div class = " button-div" v-if = " item.file && isCropper" >
< el-button type = " success" @click = " uploadFileFn(item, index)"
> 上传</ el-button
>
< el-button type = " primary" @click = " cropperFn(item, index)"
> 裁剪</ el-button
>
</ div>
</ div>
< el-upload
v-if = " multiple || (!multiple && fileList.length == 0)"
class = " avatar-uploader"
action = " #"
:accept = "
acceptArray.length > 0
? acceptArray.map((n) => acceptType[n]).join(',')
: '*'
"
:http-request = " !isCropper ? uploadFileFn : () => {}"
:multiple = " multiple"
:show-file-list = " false"
:before-upload = " beforeAvatarUpload"
>
< el-icon class = " avatar-uploader-icon" > < Plus /> </ el-icon>
</ el-upload>
</ div>
< el-dialog title = " 裁切图片" v-model = " showCropper" width = " 550px" >
< div class = " cropper-content" >
< div class = " cropper-box" >
< div class = " cropper" >
< vue-cropper
ref = " cropperRefs"
:img = " option.img"
:output-size = " option.outputSize"
:info = " option.info"
:can-scale = " option.canScale"
:auto-crop = " option.autoCrop"
:auto-crop-width = " option.autoCropWidth"
:auto-crop-height = " option.autoCropHeight"
:fixed = " option.fixed"
:fixed-number = " option.fixedNumber"
:full = " option.full"
:fixed-box = " option.fixedBox"
:can-move = " option.canMove"
:can-move-box = " option.canMoveBox"
:original = " option.original"
:center-box = " option.centerBox"
:height = " option.height"
:info-true = " option.infoTrue"
:max-img-size = " option.maxImgSize"
:enlarge = " option.enlarge"
:mode = " option.mode"
:limit-min-size = " option.limitMinSize"
/>
</ div>
</ div>
</ div>
< span slot = " footer" >
< div class = " dialog-footer" >
< el-button @click = " showCropper = false" > 取 消</ el-button>
< el-button type = " primary" @click = " onSubmit" > 确 定</ el-button>
</ div>
</ span>
</ el-dialog>
</ template>
< script setup >
import { ref, reactive, watch } from "vue" ;
import { Plus, Close, Check } from "@element-plus/icons-vue" ;
import "vue-cropper/dist/index.css" ;
import { VueCropper } from "vue-cropper" ;
const props = defineProps ( {
otherData : {
type : Object,
default : ( ) => { } ,
} ,
headers : {
type : Object,
default : ( ) => { } ,
} ,
modelValue : {
type : Array,
default : ( ) => {
return [ ] ;
} ,
} ,
multiple : {
type : Boolean,
default : false ,
} ,
size : {
type : Number,
default : 10 * 1024 * 1024 ,
} ,
isCropper : {
type : Boolean,
default : true ,
} ,
sendUrl : {
type : String,
default : "" ,
} ,
} ) ;
const emits = defineEmits ( [ "update:modelValue" ] ) ;
const cropperRefs = ref ( ) ;
const cropperCb = ref ( null ) ;
const showCropper = ref ( false ) ;
let fileList = reactive ( [ ] ) ;
const acceptArray = reactive ( [ "png" , "jpg" , "jpeg" ] ) ;
const acceptType = reactive ( {
doc : "application/msword" ,
docx : "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ,
ppt : "application/vnd.ms-powerpoint" ,
pptx : "application/vnd.openxmlformats-officedocument.presentationml.presentation" ,
xls : "application/vnd.ms-excel" ,
xlsx : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ,
pdf : "application/pdf" ,
csv : ".csv" ,
txt : "text/plain" ,
image : "image/*" ,
png : "image/png" ,
gif : "image/gif" ,
jpg : "image/jpg" ,
jpeg : "image/jpeg" ,
} ) ;
watch (
props. modelValue,
( value ) => {
const valueList = value || [ ] ;
let newFileList = [ ] ;
valueList. forEach ( ( item ) => {
const indexThis= fileList. findIndex ( n => n. src== item)
if ( indexThis== - 1 ) {
newFileList. push ( {
src : item,
isSuccess : true ,
} ) ;
}
} ) ;
fileList. unshift ( ... newFileList) ;
} ,
{ immediate : true , deep : true }
) ;
watch (
fileList,
( value ) => {
const valueList = value
. map ( ( n ) => {
if ( n. isSuccess) {
return n. src;
}
return null ;
} )
. filter ( ( n ) => n != null ) ;
emits ( "update:modelValue" , valueList) ;
} ,
{ deep : true }
) ;
const option = reactive ( {
img : "" ,
outputSize : 1 ,
outputType : "jpeg" ,
info : false ,
canScale : true ,
autoCrop : true ,
autoCropWidth : 230 ,
autoCropHeight : 150 ,
fixed : false ,
fixedNumber : [ 1.53 , 1 ] ,
full : false ,
fixedBox : false ,
canMove : true ,
canMoveBox : true ,
original : true ,
centerBox : true ,
high : false ,
infoTrue : false ,
maxImgSize : 3000 ,
enlarge : 1 ,
mode : "550px 400px" ,
limitMinSize : [ 108 , 108 ] ,
minCropBoxWidth : 108 ,
minCropBoxHeight : 108 ,
} ) ;
const judegFileSize = ( file ) => {
const filterSize = ( size ) => {
const pow1024 = ( num ) => {
return Math. pow ( 1024 , num) ;
} ;
if ( ! size) return "" ;
if ( size < pow1024 ( 1 ) ) return size + " B" ;
if ( size < pow1024 ( 2 ) ) return ( size / pow1024 ( 1 ) ) . toFixed ( 0 ) + " KB" ;
if ( size < pow1024 ( 3 ) ) return ( size / pow1024 ( 2 ) ) . toFixed ( 0 ) + " MB" ;
if ( size < pow1024 ( 4 ) ) return ( size / pow1024 ( 3 ) ) . toFixed ( 0 ) + " GB" ;
return ( size / pow1024 ( 4 ) ) . toFixed ( 2 ) + " TB" ;
} ;
let retunBoolean = true ;
let fileSize = file. size;
const fileExtArray = file. name. split ( "." ) ;
const judegFn = ( ) => {
if ( acceptArray. indexOf ( fileExtArray. at ( - 1 ) ) == - 1 ) {
alert ( ` ${ file. name} 上传失败,只能上传 ${ acceptArray. join ( "、" ) } ` ) ;
retunBoolean = false ;
}
} ;
if ( acceptArray. length > 0 ) {
if ( acceptArray. indexOf ( "image" ) != - 1 ) {
var pattern = / (\.jpg|\.jpeg|\.png|\.gif)$ / i ;
if ( ! pattern. test ( ` . ${ fileExtArray. at ( - 1 ) } ` ) ) {
judegFn ( ) ;
}
} else {
judegFn ( ) ;
}
}
if ( retunBoolean) {
if ( props. size > 0 && fileSize > props. size) {
alert ( ` 最大上传 ${ filterSize ( props. size) } ` ) ;
retunBoolean = false ;
}
}
return retunBoolean;
} ;
const beforeAvatarUpload = ( rawFile ) => {
let retunBoolean = judegFileSize ( rawFile) ;
if ( retunBoolean) {
fileList. push ( {
src : URL . createObjectURL ( rawFile) ,
file : rawFile,
} ) ;
}
return retunBoolean;
} ;
const cropperFn = ( item, index ) => {
showCropper. value = true ;
option. img = URL . createObjectURL ( item. file) ;
const reader = new FileReader ( ) ;
reader. readAsDataURL ( item. file) ;
cropperCb. value = ( res ) => {
if ( res) {
cropperRefs. value. getCropBlob ( ( data ) => {
const result = new File ( [ data] , item. file. name, {
type : item. file. type,
lastModified : Date. now ( ) ,
} ) ;
result[ "uid" ] = item. file. uid;
fileList. splice ( index, 1 , {
src : URL . createObjectURL ( result) ,
file : result,
} ) ;
showCropper. value = false ;
} ) ;
}
} ;
} ;
const delFn = ( index ) => {
fileList. splice ( index, 1 ) ;
} ;
const onSubmit = ( ) => {
if ( cropperCb. value) cropperCb. value ( true ) ;
} ;
const uploadFileFn = ( item ) => {
if ( props. sendUrl == "" ) return false ;
const successFn = ( url ) => {
const index = fileList. findIndex ( ( n ) => {
if ( n. file && n. file. uid == item. file. uid) {
return true ;
}
return false ;
} ) ;
if ( index != - 1 ) {
fileList. splice ( index, 1 , {
src : url,
file : item. file,
isSuccess : true ,
} ) ;
}
} ;
const formData = new FormData ( ) ;
formData. append ( "file" , item. file) ;
if ( props. otherData) {
Object. keys ( props. otherData) . forEach ( ( key ) => {
formData. append ( key, props. otherData[ key] ) ;
} ) ;
}
fetch ( props. sendUrl, {
method : "POST" ,
body : formData,
headers : props. headers,
"Content-type" : "multipart/form-data" ,
} )
. then ( ( respone ) => respone. json ( ) )
. then ( ( res ) => {
successFn ( "成功的url" ) ;
} )
. catch ( ( error ) => {
} ) ;
} ;
</ script>
< style scoped lang = " scss" >
.el-icon.avatar-uploader-icon {
font-size : 28px;
color : #8c939d;
border : 1px solid #ccc;
width : 178px;
height : 178px;
text-align : center;
}
.uploadMian {
vertical-align : top;
display : flex;
flex-wrap : wrap;
}
.avatar-uploader {
}
.img-item {
display : inline-block;
width : 178px;
height : 178px;
margin-right : 10px;
border : 1px solid #ccc;
position : relative;
img {
width : 100%;
height : 100%;
object-fit : contain;
position : relative;
z-index : 9;
}
&:hover {
.el-icon.uploader-close {
display : flex !important ;
}
}
.uploader-Check {
width : 40px;
height : 40px;
position : absolute;
z-index : 18;
top : 0;
left : 0;
display : flex;
background-color : #67c23a;
clip-path : polygon ( 0 0 , 100% 0, 0 100%) ;
-webkit-clip-path : polygon ( 0 0 , 100% 0, 0 100% ) ;
.el-icon {
position : absolute;
top : 4px;
left : 4px;
color : #fff;
}
}
.el-icon.uploader-close {
display : none;
position : absolute;
z-index : 20;
top : -5px;
right : -5px;
width : 20px;
height : 20px;
background-color : red;
justify-content : center;
align-items : center;
border-radius : 50%;
color : #fff;
font-size : 12px;
cursor : pointer;
}
.button-div {
position : absolute;
height : 45px;
z-index : 20;
bottom : 0;
left : 0;
width : 100%;
background-color : rgba ( 0, 0, 0, 0.2) ;
display : flex;
justify-content : space-around;
align-items : center;
}
}
.cropper-content {
display : flex;
display : -webkit-flex;
justify-content : flex-end;
.cropper-box {
width : 550px;
.cropper {
width : auto;
height : 400px;
}
}
.show-preview {
flex : 1;
-webkit-flex : 1;
display : flex;
display : -webkit-flex;
justify-content : center;
.preview {
overflow : hidden;
border : 1px solid #67c23a;
background : #cccccc;
}
}
}
.dialog-footer {
display : flex;
justify-content : center;
margin-top : 10px;
}
</ style>
5.使用:
< cropperUpload :otherData = " {a:100}" :headers = " {}" v-model = " urlList" :multiple = " true" sendUrl = " https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15" />
< script setup >
import cropperUpload from "./cropperUpload.vue" ;
const urlList = reactive ( [ 'https://img1.baidu.com/it/u=4049022245,514596079&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500' ] )
</ script>