vue裁剪gif图片并保持动画效果

简介: vue裁剪gif图片并保持动画效果

一.效果

本案例中只对gif做了处理,预留了非gif的处理,可以自行处理。文章末尾会附上案例地址。

20210530145201412.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以内的图片!&nbsp;&nbsp;支持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>

四.案例地址

cropper-gif: vue裁剪gif图片,裁剪出来的图片仍然保留动画的案例,直接down下来就可运行,效果可见:https://root181.blog.csdn.net/article/details/117398384

相关文章
|
8天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
8天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
8天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
8天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
7天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
9天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
7天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
JavaScript 测试技术 容器
Vue2+VueRouter2+webpack 构建项目
1). 安装Node环境和npm包管理工具 检测版本 node -v npm -v 图1.png 2). 安装vue-cli(vue脚手架) npm install -g vue-cli --registry=https://registry.
1050 0
|
9天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
14天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发