vue封装原生的可预览裁剪上传图片插件H5,PC端都可以使用
思路:1.先做出一个上传的图片的上传区
<!-- 上传区 --> <label for="fileUp"> <div class="upBorder"> <img src="../assets/add.png" alt="" /> <input ref="fileUp" type="file" id="fileUp" accept="image" style="display: none" @change="upload()" /> </div> </label>
upload() { let that = this; console.log(this.$refs.fileUp.files); if (this.$refs.fileUp.files.length != 0) { const reader = new FileReader(); reader.readAsDataURL(this.$refs.fileUp.files[0]); reader.onload = function () { const img = new Image(); img.src = reader.result; that.fileList.push(reader.result); that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片 console.log(reader.result); }; this.upLodaOk = true; } },
给上传图片的input绑定上ref属性然后通过FileReader构造函数获取上传的文件。
2.完成已上传文件的预览区域
<!-- 预览区域 --> <div class="preView" v-for="(i, index) in fileList" :key="index" ref="preList" > <div class="fileList" v-if="upLodaOk"> <img src="../assets/remove.png" alt="" class="remove" @click="removeProp(index)" /> <img :src="fileList[index]" alt="" class="img" @click="cut(index)" ref="imgitem" /> </div> </div>
在upload方法中将通过FileReader构造函数获取上传的文件push到fileList数组中然后遍历渲染出已经上传的图片列表,并且给每一个图片绑定ref属性。
3.完成图片删除的功能
<!-- 删除弹窗 --> <div class="prop" :style="{ height: this.windowHeight + 'px', width: this.windowWidth + 'px', }" v-if="show" > <div class="text"> <img src="../assets/remove.png" alt="" class="close" @click="removePropClose()" /> <div>要删除这张照片吗</div> <div class="action"> <button class="btn green" @click="removePropClose()">取消</button> <button class="btn blue" @click="remove()">确定</button> </div> </div> </div>
removeProp(index) { //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素 this.removeIndex = index; this.show = true; }, removePropClose() { this.show = false; }, remove() { this.fileList.splice(this.removeIndex, 1); this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片 console.log(this.$refs.fileUp.value); this.show = false; },
点击预览图片上的x会触发删除确认弹窗,在removeProp方法中将要删除的图片的Index接收并存储的removeIndex变量中,remove方法中将fileList数组中对应索引的元素去掉并且重置一下上传属性,也可以在每次上传后重置,并且关闭弹窗
4.完成上传时的剪裁功能
<!-- 裁剪蒙层 --> <div class="prop center" v-if="cutProp" :style="{ height: this.windowHeight + 'px', width: this.windowWidth + 'px', }" > <div v-html="pre" ref="preimg" class="imgContent"></div> <div class="cutHandler"> <button class="btn green" @click="cancel()">取消</button> <button class="btn blue" @click="qdcut()">剪裁</button> </div> </div>
cut(index) { this.selIndex = index; this.pre = `<img src="${this.fileList[index]}" alt="" class='cutImg' />`; this.cutProp = true; console.log(this.$refs); this.$nextTick(function () { console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点 this.myCropper = new Cropper(this.$refs.preimg.firstChild, { aspectRatio: 1 / 1, dragMode: "move", outputType: "png", //防止图片背景变黑 crop(event) { console.log(event.detail.x); console.log(event.detail.y); console.log(event.detail.width); console.log(event.detail.height); console.log(event.detail.rotate); console.log(event.detail.scaleX); console.log(event.detail.scaleY); }, }); }); }, qdcut() { let cropBox = this.myCropper.getCropBoxData(); console.log(this.myCropper.getCropBoxData()); //打印裁剪数据 let cropCanvas = this.myCropper.getCroppedCanvas({ width: cropBox.width, height: cropBox.height, }); //使用画布画出裁剪后的图片 let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据 console.log(imgData); this.fileList.splice(this.selIndex, 1, imgData); console.log(this.fileList); this.cutProp = false; }, //确定裁剪 cancel() { this.cutProp = false; }, //取消裁剪
因为本次封装的是预览时裁剪的功能,所以裁剪的是点击预览列表中的文件触发的,cut方法将选择的图片的index存储selIndex变量中,然后通过v-html指令在剪裁弹窗中加载出对应的图片来进行裁剪,裁剪使用cropper.js来进行的,注意使用时要在this.$nextTick方法的回调中来进行剪裁函数的初始化,这样才能获取到通过v-html指令插入的图片。
选择合适的裁剪尺寸后点击确认才加调用qdcut方法,通过cropper.js的内置方法getCropBoxData()获取剪裁的数据,通过getCroppedCanvas()传入对应的数据然后导出剪裁后的图片,将fileList中对应的元素替换即可完成
6.下面附上整个代码,可以直接拿去使用:
1 <template> 2 <div> 3 <!-- 裁剪蒙层 --> 4 <div 5 class="prop center" 6 v-if="cutProp" 7 :style="{ 8 height: this.windowHeight + 'px', 9 width: this.windowWidth + 'px', 10 }" 11 > 12 <div v-html="pre" ref="preimg" class="imgContent"></div> 13 <div class="cutHandler"> 14 <button class="btn green" @click="cancel()">取消</button> 15 <button class="btn blue" @click="qdcut()">剪裁</button> 16 </div> 17 </div> 18 <!-- 删除弹窗 --> 19 <div 20 class="prop" 21 :style="{ 22 height: this.windowHeight + 'px', 23 width: this.windowWidth + 'px', 24 }" 25 v-if="show" 26 > 27 <div class="text"> 28 <img 29 src="../assets/remove.png" 30 alt="" 31 class="close" 32 @click="removePropClose()" 33 /> 34 <div>要删除这张照片吗</div> 35 <div class="action"> 36 <button class="btn green" @click="removePropClose()">取消</button> 37 <button class="btn blue" @click="remove()">确定</button> 38 </div> 39 </div> 40 </div> 41 <!-- 上传区域 --> 42 <div class="upContent"> 43 <!-- 预览区域 --> 44 <div 45 class="preView" 46 v-for="(i, index) in fileList" 47 :key="index" 48 ref="preList" 49 > 50 <div class="fileList" v-if="upLodaOk"> 51 <img 52 src="../assets/remove.png" 53 alt="" 54 class="remove" 55 @click="removeProp(index)" 56 /> 57 <img 58 :src="fileList[index]" 59 alt="" 60 class="img" 61 @click="cut(index)" 62 ref="imgitem" 63 /> 64 </div> 65 </div> 66 <!-- 上传区 --> 67 <label for="fileUp"> 68 <div class="upBorder"> 69 <img src="../assets/add.png" alt="" /> 70 <input 71 ref="fileUp" 72 type="file" 73 id="fileUp" 74 accept="image" 75 style="display: none" 76 @change="upload()" 77 /> 78 </div> 79 </label> 80 </div> 81 </div> 82 </template> 83 <script> 84 import Cropper from "cropperjs"; 85 import "cropperjs/dist/cropper.css"; 86 export default { 87 name: "upload", 88 data() { 89 return { 90 cutProp: false, 91 pre: "", //准备剪裁的图片 92 selIndex: "", //选择照片的索引 93 removeIndex: "", //准备删除的照片的索引 94 show: false, //删除弹出层 95 myCropper: null, 96 afterImg: "", 97 ingData: null, 98 upLodaOk: false, //是否展示预览列表 99 fileList: [], //已经上传图片的列表 100 }; 101 }, 102 methods: { 103 upload() { 104 let that = this; 105 console.log(this.$refs.fileUp.files); 106 if (this.$refs.fileUp.files.length != 0) { 107 const reader = new FileReader(); 108 reader.readAsDataURL(this.$refs.fileUp.files[0]); 109 reader.onload = function () { 110 const img = new Image(); 111 img.src = reader.result; 112 that.fileList.push(reader.result); 113 that.$refs.fileUp.value = null; //上传后重置上传input的value,这样才能同时上传相同的图片 114 console.log(reader.result); 115 }; 116 this.upLodaOk = true; 117 } 118 }, 119 removeProp(index) { 120 //v-for循环中的ref是个数组,根据index来取每一个对应的dom元素 121 this.removeIndex = index; 122 this.show = true; 123 }, 124 removePropClose() { 125 this.show = false; 126 }, 127 remove() { 128 this.fileList.splice(this.removeIndex, 1); 129 this.$refs.fileUp.value = null; //删除后重置上传input的value,这样才能同时上传相同的图片 130 console.log(this.$refs.fileUp.value); 131 this.show = false; 132 }, 133 cut(index) { 134 this.selIndex = index; 135 this.pre = `<img 136 src="${this.fileList[index]}" 137 alt="" 138 class='cutImg' 139 />`; 140 this.cutProp = true; 141 console.log(this.$refs); 142 this.$nextTick(function () { 143 console.log(this.$refs.preimg.firstChild); //使用nextTick,dom更新完成后才能获取到子节点 144 this.myCropper = new Cropper(this.$refs.preimg.firstChild, { 145 aspectRatio: 1 / 1, 146 dragMode: "move", 147 outputType: "png", //防止图片背景变黑 148 crop(event) { 149 console.log(event.detail.x); 150 console.log(event.detail.y); 151 console.log(event.detail.width); 152 console.log(event.detail.height); 153 console.log(event.detail.rotate); 154 console.log(event.detail.scaleX); 155 console.log(event.detail.scaleY); 156 }, 157 }); 158 }); 159 }, 160 qdcut() { 161 let cropBox = this.myCropper.getCropBoxData(); 162 console.log(this.myCropper.getCropBoxData()); //打印裁剪数据 163 let cropCanvas = this.myCropper.getCroppedCanvas({ 164 width: cropBox.width, 165 height: cropBox.height, 166 }); //使用画布画出裁剪后的图片 167 let imgData = cropCanvas.toDataURL(); //导出裁剪后图片的数据 168 console.log(imgData); 169 this.fileList.splice(this.selIndex, 1, imgData); 170 console.log(this.fileList); 171 this.cutProp = false; 172 }, //确定裁剪 173 cancel() { 174 this.cutProp = false; 175 }, //取消裁剪 176 }, 177 mounted() {}, 178 computed: { 179 windowWidth() { 180 return document.documentElement.clientWidth; 181 }, 182 windowHeight() { 183 return document.documentElement.clientHeight; 184 }, 185 }, //监听屏幕的宽度和高度 186 }; 187 </script> 188 <style> 189 .upBorder { 190 width: 8rem; 191 height: 8rem; 192 border: 1px silver dashed; 193 display: flex; 194 justify-content: center; 195 align-items: center; 196 } 197 .upContent { 198 display: flex; 199 justify-content: center; 200 align-items: center; 201 } 202 .img { 203 width: 8rem; 204 height: 8rem; 205 } 206 207 .fileList { 208 position: relative; 209 display: flex; 210 flex-direction: column; 211 justify-content: center; 212 align-items: center; 213 } 214 .remove { 215 position: absolute; 216 width: 1rem; 217 height: 1rem; 218 top: 0rem; 219 right: 0rem; 220 cursor: pointer; 221 } 222 .prop { 223 vertical-align: middle; 224 position: fixed; 225 top: 0; 226 left: 0; 227 z-index: 999; 228 background-color: rgba(0, 0, 0, 0.7); 229 } 230 .text { 231 border-radius: 0.2rem; 232 top: 50%; 233 left: 50%; 234 -webkit-transform: translate3d(-50%, -50%, 0); 235 transform: translate3d(-50%, -50%, 0); 236 position: fixed; 237 z-index: 1000; 238 color: black; 239 text-align: center; 240 background-color: #fff; 241 padding: 2rem 4rem; 242 white-space: nowrap; 243 } 244 .close { 245 position: absolute; 246 top: 0.3rem; 247 right: 0.3rem; 248 width: 1rem; 249 height: 1rem; 250 } 251 .action { 252 display: flex; 253 justify-content: space-between; 254 align-items: center; 255 margin-top: 1rem; 256 } 257 .btn { 258 font-size: 0.12rem; 259 color: #fff; 260 padding: 0.2rem 0.8rem; 261 } 262 .blue { 263 background-color: #1989fa; 264 border: 1px solid #1989fa; 265 } 266 .green { 267 background-color: #07c160; 268 border: 1px solid #07c160; 269 } 270 .cropper-point.point-se { 271 width: 5px; 272 height: 5px; 273 } 274 .cropper { 275 position: fixed; 276 top: 0; 277 z-index: 999; 278 } 279 280 /* .cropper-container{ 281 top: 50%; 282 left: 50%; 283 -webkit-transform: translate3d(-50%, -50%, 0); 284 transform: translate3d(-50%, -50%, 0); 285 } */ 286 .imgContent { 287 width: 16rem; 288 height: 16rem; 289 display: inline-block; 290 /* top: 50%; 291 left: 50%; 292 -webkit-transform: translate3d(-50%, -50%, 0); 293 transform: translate3d(-50%, -50%, 0); */ 294 } 295 .cutImg { 296 display: block; 297 max-width: 100%; 298 } 299 .center { 300 display: flex; 301 flex-direction: column; 302 justify-content: center; 303 align-items: center; 304 } 305 .cropper-bg { 306 background: none; 307 } 308 .cutHandler { 309 margin-top: 2rem; 310 width: 16rem; 311 text-align: center; 312 display: flex; 313 justify-content: space-between; 314 align-items: center; 315 } 316 .cropper-modal { 317 background: rgba(0, 0, 0, 0); 318 } 319 </style>
运行截图:
H5,PC端都可以使用