Recommended reading

Front-end structure: blog.csdn.net/qq_32442973…

Effect of a.

In this case, only GIFs are processed, and non-GIFs are reserved for self-processing. The case address will be attached at the end of the article.

This image is a bit big, please be patient for 2 seconds.

2. Train of thought

It is equivalent to taking the position and width and height of the clipping frame in the picture, and then drawing GIF again according to the frame number, taking the size of the drawing area and the coordinates of the four vertices.

Code 3.

<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 readFile from "js-file-reader";
import GIF from 'gif.js'
import { GifToCanvas } from '@/libs/gifToCanvas.js'
function 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 });
}
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();
    },
    async changeFile(e) {
      let file = e.target.files[0];
      if (file) {
        console.log("file", file);
        this.fileinfo = file //保存file信息
        this.imgType = file.type;
        let RESULT = await readFile(file);
        console.log(RESULT);
        this.cutImgUrl = RESULT[RESULT.length - 1].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'){
        let blob = await this.cropGifHandle()
        this.previewUrl =  window.URL.createObjectURL(blob)
        console.log('previewUrl',this.previewUrl)
      }else{

      }
      this.cutLoading = false
      this.cropFlag = false
    },
    //gif裁剪
    async cropGifHandle() {
      return new Promise((resolve, reject) => {
        if (this.myCropper) {
          const url = URL.createObjectURL(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>
Copy the code

Iv. Case address

Cropper – GIF: vue tailoring GIF images, cut out the picture remains the case of animation, can be run directly down down, the effect is: https://root181.blog.csdn.net/article/details/117398384