一.效果
本案例中只对gif做了处理,预留了非gif的处理,可以自行处理。文章末尾会附上案例地址。
二.思路
相当于就是取裁剪框在图片中的位置和宽高,再根据帧数,取绘制区域大小及四个顶点的坐标绘制一遍gif。
三.代码
<template> <div id="app"> <div class="main cut"> <div class="cut-upload-wrap cut-model1"> <div class="cut-upload-container"> <div class="cut-upload-main"> <div class="cut-upload-btn" @click="uploadBtn()"> 上传图片 </div> <input type="file" style="opacity: 0;" accept="image/gif,image/png,image/jpeg,image/jpg" class="cut-upload-file com-input-avatar" ref="J-uploadBtn" id="J-uploadBtn" @change="changeFile" /> </div> <div class="cut-upload-tip"> 请上传50M以内的图片! 支持GIF、PNG、JPG、JPEG </div> </div> <div class="priview-box"> <img :src="previewUrl" alt="" v-if="previewUrl"> <span v-else style="color: #666;">暂未裁剪图片!</span> </div> </div> </div> <el-dialog title="裁剪" :visible.sync="cropFlag" append-to-body :destroy-on-close="true" > <div class="cropper-content"> <div class="cropper" style="text-align: center"> <img id="image" ref="cropper-img" :src="cutImgUrl" /> </div> </div> <div slot="footer" class="dialog-footer"> <el-button @click="closeCut()">取 消</el-button> <el-button type="primary" @click="finishCut()" :loading="cutLoading" >{{cutLoading?'裁剪中...':'确定'}}</el-button > </div> </el-dialog> </div> </template> <script> import $ from "jquery"; import Cropper from "cropperjs"; import "cropperjs/dist/cropper.css"; import GIF from 'gif.js' import { GifToCanvas } from '@/libs/gifToCanvas.js' export default { name: "App", components: {}, data() { return { imgType: "image/gif", cutImgUrl: "", myCropper: "", cropFlag: false, cutLoading: false, gifToCanvas:'', gif:'', previewUrl:'', }; }, methods: { uploadBtn() { let uploadBtn = $("#J-uploadBtn"); uploadBtn.click(); }, dataURLtoBlob(dataurl) { var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while (n--) { u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], { type: mime }); }, fileToBase64(file){ return new Promise((resolve)=>{ let reader = new FileReader(); reader.onload = function (evt) { let base64 = evt.target.result; resolve(base64) }; reader.readAsDataURL(file); }) }, async changeFile(e) { let file = e.target.files[0]; if (file) { this.fileinfo = file //保存file信息 this.imgType = file.type; this.cutImgUrl = await this.fileToBase64(file);//file转base64 if (file.type == "image/gif") { //gif图片 this.cropFlag = true; this.$nextTick(() => { setTimeout(() => { this.myCropper = new Cropper(this.$refs["cropper-img"], { aspectRatio: 300 / 300, 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); }, }); }, 0); }); } else { //非gif图片 alert('请上传gif格式图片,本工程只处理gif图,但预留了非gif逻辑空间,有需要请自行补充!') } } }, //关闭裁剪 closeCut(){ this.cropFlag = false; this.cutLoading = false; if(this.myCropper){ this.myCropper.destroy() } if(this.gif){ this.gif = '' } if(this.gifToCanvas){ this.gifToCanvas.clear() } }, async finishCut() { if(this.cutLoading)return this.cutLoading = true if(this.imgType == 'image/gif'){//gif处理 let blob = await this.cropGifHandle() this.previewUrl = window.URL.createObjectURL(blob) }else{//预留png } this.cutLoading = false this.cropFlag = false }, //gif裁剪 async cropGifHandle() { return new Promise((resolve, reject) => { if (this.myCropper) { const url = URL.createObjectURL(this.dataURLtoBlob(this.myCropper.url)); const cropBoxData = this.myCropper.getCropBoxData(); const canvasData = this.myCropper.getCanvasData(); this.gifToCanvas = new GifToCanvas(url, { targetOffset: { dx: cropBoxData.left - canvasData.left, dy: cropBoxData.top - canvasData.top, width: canvasData.width, height: canvasData.height, sWidth: cropBoxData.width, sHeight: cropBoxData.height, }, }); this.gif = new GIF({ workers: 4, quality: 10, width: cropBoxData.width, height: cropBoxData.height, workerScript: `${window.location.origin}/gif.worker.js`, }); const addFrame = (canvas, delay) => { this.gif.addFrame(canvas, { copy: true, delay }); }; this.gifToCanvas.on("progress", (canvas, delay) => { addFrame(canvas, delay); }); this.gifToCanvas.on("finished", (canvas, delay) => { addFrame(canvas, delay); this.gif.render(); }); this.gif.on("finished", (blob) => { console.log("finished", window.URL.createObjectURL(blob)); resolve(blob); }); this.gifToCanvas.init(); } else { reject(); } }); }, }, }; </script> <style lang="less"> #app { background: #000; width: 100%; height: 100%; min-height: 100vh; padding-top: 100px; box-sizing: border-box; .main { width: 1200px; margin: 0 auto; box-sizing: border-box; background: #1b1b1b; &.cut { min-height: 424px !important; padding: 20px; margin-bottom: 30px; box-sizing: border-box; .cut, .cut-upload-main, .cut-upload-wrap { position: relative; } .cut { height: 100%; min-height: 424px !important; padding: 20px; margin-bottom: 30px; } .cut-model2 { display: none; } .cut-upload-wrap { text-align: center; top: 50%; margin-top: 100px; display: flex; justify-content: space-around; } .cut-upload-container { display: inline-block; padding: 35px; border: 5px dashed #262626; } .cut-info, .cut-upload-tip { padding-top: 20px; font-size: 14px; } .cut-upload-btn { width: 385px; height: 60px; line-height: 60px; color: #fff; background: #6418ff; -webkit-transition: 0.2s; -o-transition: 0.2s; transition: 0.2s; cursor: pointer; } .cut-upload-main:hover .cut-upload-btn { background: #5e12fb; } .cut-upload-file { position: absolute; } .cut-upload-tip { color: #666; } } } } </style>