vue 图片裁剪组件
简介
图片裁剪组件同上传图片组件一样,用的较多,这里主要用的是 vue-cropper 这个包,功能支持裁剪图片文件类型检验,图片大小检验,图片分辨率校验以及图片比列校验等功能。
主要依赖说明 (先安装,步骤略)
| 1 | { | 
| 2 |   "element-ui": "2.11.1", | 
| 3 |   "vue": "^2.6.10", | 
| 4 |   "vue-router": "^3.0.1", | 
| 5 |   "vue-cropper": "^0.4.7" | 
| 6 | } | 
正文
1.组件
src/components/Cropper.vue
| 1 | <template> | 
| 2 |   <div class="custom-upload"> | 
| 3 |     <el-dialog | 
| 4 |       title="图片裁剪" | 
| 5 |       :visible.sync="showCropper" | 
| 6 |       top="6vh" | 
| 7 |       width="50%" | 
| 8 |       height="600" | 
| 9 |       class="cropper-dialog" | 
| 10 |       center | 
| 11 |       append-to-body | 
| 12 |     > | 
| 13 |       <vue-cropper | 
| 14 |         v-if="showCropper" | 
| 15 |         id="corpper" | 
| 16 |         ref="cropper" | 
| 17 |         :class="{'corpper-warp':showCropper}" | 
| 18 |         v-bind="cropper" | 
| 19 |       /> | 
| 20 |       <div v-if="showCropper" class="cropper-button"> | 
| 21 |         <el-button | 
| 22 |           class="cancel-btn" | 
| 23 |           size="small" | 
| 24 |           @click.native="showCropper=false" | 
| 25 |           >取消</el-button | 
| 26 |         > | 
| 27 |         <el-button | 
| 28 |           size="small" | 
| 29 |           type="primary" | 
| 30 |           :loading="loading" | 
| 31 |           @click="uploadCover" | 
| 32 |           >完成</el-button | 
| 33 |         > | 
| 34 |       </div> | 
| 35 |     </el-dialog> | 
| 36 |     <input | 
| 37 |       :id="id" | 
| 38 |       type="file" | 
| 39 |       style="display: none" | 
| 40 |       name="single" | 
| 41 |       accept="image/*" | 
| 42 |       @change="onChange($event)" | 
| 43 |     /> | 
| 44 | |
| 45 |     <el-button | 
| 46 |       size="small" | 
| 47 |       type="primary" | 
| 48 |       :loading="loading" | 
| 49 |       @click="handleOpenFile()" | 
| 50 |     > | 
| 51 |       <i class="fa fa-upload" /> | 
| 52 |       {{ buttonName }} | 
| 53 |     </el-button> | 
| 54 |     <div v-if="tips" class="tips clear-margin-top">{{ tips }}</div> | 
| 55 |   </div> | 
| 56 | </template> | 
| 57 | |
| 58 | <script> | 
| 59 |   // 上传文件组件 | 
| 60 |   import { VueCropper } from "vue-cropper"; | 
| 61 | |
| 62 |   // 定义的接口根据自己项目更换 | 
| 63 |   import { uploadImage } from "@/api/upload"; | 
| 64 | |
| 65 |   import { isImageFile, isMaxFileSize, readFile } from "@/utils/upload"; // 见下文 | 
| 66 |   import { Message } from "element-ui"; | 
| 67 | |
| 68 |   export default { | 
| 69 |     components: { | 
| 70 |       VueCropper | 
| 71 |     }, | 
| 72 |     props: { | 
| 73 |       // 最大上传文件的大小 | 
| 74 |       maxFileSize: { | 
| 75 |         type: Number, | 
| 76 |         default: 2 // (MB) | 
| 77 |       }, | 
| 78 |       // 按钮文字 | 
| 79 |       buttonName: { | 
| 80 |         type: String, | 
| 81 |         default: "添加图片" | 
| 82 |       }, | 
| 83 |       // 提示内容 | 
| 84 |       tips: { | 
| 85 |         type: String | 
| 86 |       }, | 
| 87 |       // 图片裁剪比列 | 
| 88 |       fixedNumber: { | 
| 89 |         type: Array, | 
| 90 |         default: function() { | 
| 91 |           return []; | 
| 92 |         } | 
| 93 |       }, | 
| 94 |       // 图片文件分辨率的宽度 | 
| 95 |       width: { | 
| 96 |         type: Number, | 
| 97 |         default: 460 | 
| 98 |       }, | 
| 99 |       // 图片文件分辨率的高度 | 
| 100 |       height: { | 
| 101 |         type: Number, | 
| 102 |         default: 300 | 
| 103 |       } | 
| 104 |     }, | 
| 105 |     data() { | 
| 106 |       return { | 
| 107 |         id: "cropper-input-" + +new Date(), | 
| 108 |         loading: false, | 
| 109 |         showCropper: false, | 
| 110 |         cropper: { | 
| 111 |           img: "", | 
| 112 |           info: true, | 
| 113 |           size: 0.9, | 
| 114 |           outputType: "png", | 
| 115 |           canScale: true, | 
| 116 |           autoCrop: true, | 
| 117 |           full: true, | 
| 118 |           // 只有自动截图开启 宽度高度才生效 | 
| 119 |           autoCropWidth: this.width, | 
| 120 |           autoCropHeight: this.height, | 
| 121 |           fixedBox: false, | 
| 122 |           // 开启宽度和高度比例 | 
| 123 |           fixed: true, | 
| 124 |           fixedNumber: this.fixedNumber, | 
| 125 |           original: false, | 
| 126 |           canMoveBox: true, | 
| 127 |           canMove: true | 
| 128 |         } | 
| 129 |       }; | 
| 130 |     }, | 
| 131 |     methods: { | 
| 132 |       // 打开文件 | 
| 133 |       handleOpenFile() { | 
| 134 |         const input = document.getElementById(this.id); | 
| 135 |         // 解决同一个文件不能监听的问题 | 
| 136 |         input.addEventListener( | 
| 137 |           "click", | 
| 138 |           function() { | 
| 139 |             this.value = ""; | 
| 140 |           }, | 
| 141 |           false | 
| 142 |         ); | 
| 143 |         // 点击input | 
| 144 |         input.click(); | 
| 145 |       }, | 
| 146 | |
| 147 |       // 裁剪input 监听 | 
| 148 |       async onChange(e) { | 
| 149 |         const file = e.target.files[0]; | 
| 150 |         if (!file) { | 
| 151 |           return Message.error("选择图片失败"); | 
| 152 |         } | 
| 153 |         // 验证文件类型 | 
| 154 |         if (!isImageFile(file)) { | 
| 155 |           return; | 
| 156 |         } | 
| 157 |         try { | 
| 158 |           // 读取文件 | 
| 159 |           const src = await readFile(file); | 
| 160 |           this.showCropper = true; | 
| 161 |           this.cropper.img = src; | 
| 162 |         } catch (error) { | 
| 163 |           console.log(error); | 
| 164 |         } | 
| 165 |       }, | 
| 166 | |
| 167 |       // 封面上传功能 | 
| 168 |       uploadCover() { | 
| 169 |         this.$refs.cropper.getCropBlob(async imgRes => { | 
| 170 |           try { | 
| 171 |             // 文件大小限制 | 
| 172 |             if (!isMaxFileSize(imgRes, this.maxFileSize)) { | 
| 173 |               return; | 
| 174 |             } | 
| 175 |             this.loading = true; | 
| 176 |             const url = await uploadImage(imgRes); | 
| 177 |             this.$emit("subUploadSucceed", url); | 
| 178 |             Message.success("上传成功"); | 
| 179 |             this.loading = false; | 
| 180 |             this.showCropper = false; | 
| 181 |           } catch (error) { | 
| 182 |             this.loading = false; | 
| 183 |             this.showCropper = false; | 
| 184 |             Message.error(error.data.message); | 
| 185 |           } | 
| 186 |         }); | 
| 187 |       } | 
| 188 |     } | 
| 189 |   }; | 
| 190 | </script> | 
| 191 | |
| 192 | <style lang="scss"> | 
| 193 |   #corpper { | 
| 194 |     width: 90%; | 
| 195 |     height: 400px; | 
| 196 |     margin: 0 auto; | 
| 197 |     background-image: none; | 
| 198 |     background: #fff; | 
| 199 |     z-index: 1002; | 
| 200 |   } | 
| 201 |   .cropper-dialog { | 
| 202 |     height: 800px; | 
| 203 |     text-align: center; | 
| 204 |     .el-dialog__header { | 
| 205 |       padding-top: 15px; | 
| 206 |     } | 
| 207 |     .el-dialog--center .el-dialog__body { | 
| 208 |       padding-top: 0; | 
| 209 |       padding-bottom: 15px; | 
| 210 |     } | 
| 211 |     .el-dialog { | 
| 212 |       text-align: center; | 
| 213 |     } | 
| 214 |   } | 
| 215 |   .cropper-button { | 
| 216 |     z-index: 1003; | 
| 217 |     text-align: center; | 
| 218 |     margin-top: 20px; | 
| 219 |     .el-button { | 
| 220 |       font-size: 16px; | 
| 221 |       cursor: pointer; | 
| 222 |       text-align: center; | 
| 223 |     } | 
| 224 |     .cancel-btn { | 
| 225 |       color: #373737; | 
| 226 |     } | 
| 227 |     .el-button:last-child { | 
| 228 |       margin-left: 100px; | 
| 229 |     } | 
| 230 |   } | 
| 231 |   .cropper-modal { | 
| 232 |     background-color: rgba(0, 0, 0, 0.5) !important; | 
| 233 |   } | 
| 234 |   .custom-upload { | 
| 235 |     .tips { | 
| 236 |       margin-top: 10px; | 
| 237 |       color: red; | 
| 238 |       font-size: 12px; | 
| 239 |     } | 
| 240 |     .clear-margin-top { | 
| 241 |       margin-top: 0; | 
| 242 |     } | 
| 243 |   } | 
| 244 | </style> | 
2.使用
| 1 | <template> | 
| 2 |   <div v-if="url"> | 
| 3 |     <img :src="url" height="160" /> | 
| 4 |   </div> | 
| 5 |   <div> | 
| 6 |     <App-cropper | 
| 7 |       :width="300" | 
| 8 |       :height="300" | 
| 9 |       :fixed-number="[1,1]" | 
| 10 |       @subUploadSucceed="getShopImages" | 
| 11 |     /> | 
| 12 |   </div> | 
| 13 | </template> | 
| 14 | |
| 15 | <script> | 
| 16 |   import AppCropper from "@/components/Cropper"; | 
| 17 |   export default { | 
| 18 |     name: "GoodsForm", | 
| 19 |     components: { | 
| 20 |       AppCropper | 
| 21 |     }, | 
| 22 |     data() { | 
| 23 |       return { | 
| 24 |         url: "" | 
| 25 |       }; | 
| 26 |     }, | 
| 27 |     methods: { | 
| 28 |       // 海报上传成功 | 
| 29 |       handleUploadSucceed(url) { | 
| 30 |         this.url = url; | 
| 31 |       } | 
| 32 |     } | 
| 33 |   }; | 
| 34 | </script> | 
3.补充 src/utils/upload.js 文件
| 1 | import { Message } from "element-ui"; | 
| 2 | |
| 3 | /** | 
| 4 |  * | 
| 5 |  * @param {file} file 源文件 | 
| 6 |  * @desc 限制为图片文件 | 
| 7 |  * @retutn 是图片文件返回true否则返回false | 
| 8 |  */ | 
| 9 | export const isImageFile = (file, fileTypes) => { | 
| 10 |   const types = fileTypes || [ | 
| 11 |     "image/png", | 
| 12 |     "image/gif", | 
| 13 |     "image/jpeg", | 
| 14 |     "image/jpg", | 
| 15 |     "image/bmp", | 
| 16 |     "image/x-icon" | 
| 17 |   ]; | 
| 18 |   const isImage = types.includes(file.type); | 
| 19 |   if (!isImage) { | 
| 20 |     Message.error("上传文件非图片格式!"); | 
| 21 |     return false; | 
| 22 |   } | 
| 23 | |
| 24 |   return true; | 
| 25 | }; | 
| 26 | |
| 27 | /** | 
| 28 |  * | 
| 29 |  * @param {file} file 源文件 | 
| 30 |  * @param {number} fileMaxSize  图片限制大小单位(MB) | 
| 31 |  * @desc 限制为文件上传大小 | 
| 32 |  * @retutn 在限制内返回true否则返回false | 
| 33 |  */ | 
| 34 | export const isMaxFileSize = (file, fileMaxSize = 2) => { | 
| 35 |   const isMaxSize = file.size / 1024 / 1024 < fileMaxSize; | 
| 36 |   if (!isMaxSize) { | 
| 37 |     Message.error("上传头像图片大小不能超过 " + fileMaxSize + "MB!"); | 
| 38 |     return false; | 
| 39 |   } | 
| 40 |   return true; | 
| 41 | }; | 
| 42 | |
| 43 | /** | 
| 44 |  * | 
| 45 |  * @param {file} file 源文件 | 
| 46 |  * @desc 读取图片文件为base64文件格式 | 
| 47 |  * @retutn 返回base64文件 | 
| 48 |  */ | 
| 49 | export const readFile = file => { | 
| 50 |   return new Promise((resolve, reject) => { | 
| 51 |     const reader = new FileReader(); | 
| 52 |     reader.onload = e => { | 
| 53 |       const data = e.target.result; | 
| 54 |       resolve(data); | 
| 55 |     }; | 
| 56 |     reader.onerror = () => { | 
| 57 |       const err = new Error("读取图片失败"); | 
| 58 |       reject(err.message); | 
| 59 |     }; | 
| 60 | |
| 61 |     reader.readAsDataURL(file); | 
| 62 |   }); | 
| 63 | }; | 
| 64 | |
| 65 | /** | 
| 66 |  * | 
| 67 |  * @param {string} src  图片地址 | 
| 68 |  * @desc 加载真实图片 | 
| 69 |  * @return 读取成功返回图片真实宽高对象 ag: {width:100,height:100} | 
| 70 |  */ | 
| 71 | export const loadImage = src => { | 
| 72 |   return new Promise((resolve, reject) => { | 
| 73 |     const image = new Image(); | 
| 74 |     image.src = src; | 
| 75 |     image.onload = () => { | 
| 76 |       const data = { | 
| 77 |         width: image.width, | 
| 78 |         height: image.height | 
| 79 |       }; | 
| 80 |       resolve(data); | 
| 81 |     }; | 
| 82 |     image.onerror = () => { | 
| 83 |       const err = new Error("加载图片失败"); | 
| 84 |       reject(err); | 
| 85 |     }; | 
| 86 |   }); | 
| 87 | }; | 
| 88 | |
| 89 | /** | 
| 90 |  * | 
| 91 |  * @param {file} file 源文件 | 
| 92 |  * @param {object} props   文件分辨率的宽和高   ag: props={width:100, height :100} | 
| 93 |  * @desc  判断图片文件的分辨率是否在限定范围之内 | 
| 94 |  * @throw  分辨率不在限定范围之内则抛出异常 | 
| 95 |  * | 
| 96 |  */ | 
| 97 | export const isAppropriateResolution = async (file, props) => { | 
| 98 |   try { | 
| 99 |     const { width, height } = props; | 
| 100 |     const base64 = await readFile(file); | 
| 101 |     const image = await loadImage(base64); | 
| 102 |     if (image.width !== width || image.height !== height) { | 
| 103 |       throw new Error("上传图片的分辨率必须为" + width + "*" + height); | 
| 104 |     } | 
| 105 |   } catch (error) { | 
| 106 |     throw error; | 
| 107 |   } | 
| 108 | }; | 
| 109 | |
| 110 | /** | 
| 111 |  * | 
| 112 |  * @param {file} file 源文件 | 
| 113 |  * @param {array} ratio   限制的文件比例 ag:  ratio= [1,1] | 
| 114 |  * @desc 判断图片文件的比列是否在限定范围 | 
| 115 |  * @throw  比例不在限定范围之内则抛出异常 | 
| 116 |  */ | 
| 117 | export const isAppRatio = async (file, ratio) => { | 
| 118 |   try { | 
| 119 |     const [w, h] = ratio; | 
| 120 |     if (h === 0 || w === 0) { | 
| 121 |       const err = "上传图片的比例不能出现0"; | 
| 122 |       Message.error(err); | 
| 123 |       throw new Error(err); | 
| 124 |     } | 
| 125 |     const base64 = await readFile(file); | 
| 126 |     const image = await loadImage(base64); | 
| 127 |     if (image.width / image.height !== w / h) { | 
| 128 |       throw new Error("上传图片的宽高比例必须为 " + w + " : " + h); | 
| 129 |     } | 
| 130 |   } catch (error) { | 
| 131 |     throw error; | 
| 132 |   } | 
| 133 | }; | 
