Writing articles on the mobile terminal, emoticons, picture cropping (gif cropping), picture preview

Effect

Please add a picture description

source address

cropperjs crops the picture

address cropperjs

Implementation ideas

  • Get the array to be cropped passed by the parent component
  • The mounted stage judges whether the current image type is a gif image, and performs different operations to instantiate the components and save them in the data
croppering() {
    
    
  // 是否是gif图片
  if (this.imageUrl[this.index].isGif) {
    
    
    this.gifInit();
  } else {
    
    
    this.init();
  }
},
  • Normal image manipulation
init() {
    
    
      this.myCropper = new Cropper(this.$refs.image, {
    
    
        viewMode: 2,
        dragMode: 'crop',
        initialAspectRatio: 1,
        // aspectRatio: 1,
        checkOrientation: false,
        checkCrossOrigin: false,
        guides: false,
        center: false,
        background: false,
        autoCropArea: 0.8, // 裁剪框为图片大小的80%
        zoomOnWheel: false,
        movable: false,
        rotatable: false,
        scalable: false,
        zoomOnTouch: false,
      });
    },
  • When clicking next, the current image is cropped and output, and passed to the parent component, and judges whether there is still a need to crop the image
this.afterImg = this.myCropper.getCroppedCanvas({
    
    
   imageSmoothingQuality: 'high',
}).toDataURL('image/jpeg');
 this.index++;
 /**
  * 向父组件传递裁剪过后的值
  * 判断是否还有数据,有的话再次调用croper进行实例化操作
  */
 if (this.imageUrl.length > this.index) {
    
     
   // 通知父组件保存值
   this.$emit('imgCropped', {
    
     src: this.afterImg, id: this.imageUrl[this.index - 1].id });
   this.nextImg();
 } else {
    
    
   this.$emit('imgCropped', {
    
     src: this.afterImg, id: this.imageUrl[this.index - 1].id });
   this.$emit('closeCropper');
 }
  • Since cropperjs does not support gif cropping, it is necessary to introduce libgif.js (split gif images) and gif.js (merge gif images), and then perform cropping operations like ordinary images
  • gif image cropping initialization
gifInit() {
    
    
 this.pre_load_gif(this.imageUrl[this.index].url);
},
  • libgif.js splits gif and saves the picture in img_list
dataURLtoFile(dataurl, filename) {
    
    
   const arr = dataurl.split(',');
   const mime = arr[0].match(/:(.*?);/)[1];
   const bstr = atob(arr[1]);
   let n = bstr.length;
   const u8arr = new Uint8Array(n);
   while (n--) {
    
    
     u8arr[n] = bstr.charCodeAt(n);
   }
   return new File([u8arr], filename, {
    
     type: mime });
 },
 // 将canvas转换成file对象
 convertCanvasToImage(canvas, filename) {
    
    
   return this.dataURLtoFile(canvas.toDataURL('image/png'), filename);
 },
 base64ToBlob(base64) {
    
    
   const parts = base64.split(';base64,');
   const contentType = parts[0].split(':')[1];
   const raw = window.atob(parts[1]);
   const rawLength = raw.length;
   const uInt8Array = new Uint8Array(rawLength);
   for (let i = 0; i < rawLength; i += 1) {
    
    
     uInt8Array[i] = raw.charCodeAt(i);
   }
   return new Blob([uInt8Array], {
    
     type: contentType });
 },
pre_load_gif(gif_source) {
    
    
  // var img_list = [];
   const gifImg = document.createElement('img');
   // 转换成blob类型
   const gif = this.base64ToBlob(gif_source);
   // gif库需要img标签配置下面两个属性
   gifImg.setAttribute('rel:animated_src', URL.createObjectURL(gif));
   gifImg.setAttribute('rel:auto_play', '1');
   document.body.appendChild(gifImg);
   // console.log(gifImg);
   // 新建gif实例
   const rub = new SuperGif({
    
     gif: gifImg });
   rub.load(() => {
    
    
     const img_list = [];
     for (let i = 1; i <= rub.get_length(); i++) {
    
    
       // 遍历gif实例的每一帧
       rub.move_to(i);
       // 将每一帧的canvas转换成file对象
       const cur_file = this.convertCanvasToImage(rub.get_canvas(), `'test'-${
      
      i}`);
       img_list.push({
    
    
         file_name: cur_file.name,
         url: URL.createObjectURL(cur_file),
         file: cur_file,
       });
     }
     this.img_list = img_list;
     this.gifCropper();
   });
   gifImg.remove();
},
  • Crop each frame of picture according to the previous idea, if there are too many pictures, it will be automatically cropped
gifCropper() {
    
    
  this.showItemImg = this.img_list[this.img_list_index].url;
  this.$nextTick(() => {
    
    
    this.myCropper = new Cropper(this.$refs.image, {
    
    
      viewMode: 1,
      dragMode: 'none',
      checkOrientation: false,
      checkCrossOrigin: false,
      guides: false,
      center: false,
      background: false,
      autoCropArea: 1,
      zoomOnWheel: false,
      movable: false,
      rotatable: false,
      scalable: false,
      zoomOnTouch: false, // 允许缩放图片
      ready: (e) => {
    
    
        // 开始剪裁
        this.sureSavaGif(e.srcElement.width);
      },
    });
  });
},
sureSavaGif(imgWidth) {
    
    
  if (imgWidth > 800) {
    
    
    // eslint-disable-next-line no-param-reassign
    imgWidth = 800;
  }
  const afterImg = this.myCropper.getCroppedCanvas({
    
    
    imageSmoothingQuality: 'height',
    width: imgWidth,
  }).toDataURL('image/jpeg');
  this.eachGif.push(afterImg);
  // 销毁实例
  this.myCropper.destroy();
  // 判断gif是否每一帧都裁剪完毕
  this.img_list_index++;
  if (this.img_list.length > this.img_list_index) {
    
    
    this.$nextTick(() => {
    
    
      this.gifCropper();
    });
  } else {
    
    
    // this.img_list_index = 0;
    // this.eachGif = [];
    this.mergeGif();
  }
},
  • Merge gifs
// gif合并
async mergeGif() {
    
    
   // const width=300;
   // const height=300;
   const gif = new GIF({
    
    
     workers: 2,
     quality: 10,
     // width,
     // height,
     workerScript: getGifWorker(), // 自定义worker地址
   });
   let j = 0;
   const canvas = document.createElement('canvas');
   const ctx = canvas.getContext('2d');
   for (let i = 1; i <= this.eachGif.length; i++) {
    
    
     // eslint-disable-next-line no-await-in-loop
     const imgImage = await this.loading(i);
     canvas.width = imgImage.width;
     canvas.height = imgImage.height;
     ctx.fillStyle = '#fff';
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     ctx.drawImage(imgImage, 0, 0, canvas.width, canvas.height);
     gif.addFrame(canvas, {
    
     copy: true, delay: 50 });
     j++;
     if (j >= this.eachGif.length) {
    
    
       gif.render();
     }
   }
   gif.on('finished', async (blob) => {
    
    
     const result = await this.blobToBase64(blob);
     console.log(result);
     console.log(this.index);
     this.$emit('imgCropped', {
    
     src: result, id: this.imageUrl[this.index].id });
     // 生成图片链接
     // const url = URL.createObjectURL(blob);
     // console.log(url);
     // const a = document.createElement('a');
     // a.href = URL.createObjectURL(blob);
     // a.download = 'test.gif';
     // a.click();
     gif.abort();
     const gifsNode = document.getElementsByClassName('jsgif');
     console.log(gifsNode[0]);
     gifsNode[0].remove();
     // 是否还要裁剪
     this.index++;
     console.log(this.imageUrl);
     if (this.imageUrl.length > this.index) {
    
    
       // eslint-disable-next-line no-unused-expressions
       this.eachGif = [];
       this.img_list_index = 0;
       this.croppering();
     } else {
    
    
       this.index = 0;
       this.img_list_index = 0;
       // this.$refs.input.value = '';
       this.$emit('closeCropper');
     }
   });
 },
 loading(i) {
    
    
   return new Promise((resolve) => {
    
    
     const imgImage = new Image();
     imgImage.src = this.eachGif[i - 1];
     document.body.appendChild(imgImage);
     // console.log(imgImage);
     imgImage.onload = () => {
    
    
       resolve(imgImage);
       imgImage.parentNode.removeChild(imgImage);
     };
   });
 },
 blobToBase64(blob) {
    
    
   return new Promise((resolve, reject) => {
    
    
     const fileReader = new FileReader();
     fileReader.onload = (e) => {
    
    
       resolve(e.target.result);
     };
     // readAsDataURL
     fileReader.readAsDataURL(blob);
     fileReader.onerror = () => {
    
    
       reject(new Error('blobToBase64 error'));
     };
   });
 },
  • Click Close to close the clipper
goBack() {
    
    
    // 关闭剪裁器
     this.$emit('closeCropper');
 },

Photoswipe realizes the preview operation of pictures

addressphotoswipe _

Implementation ideas

  • The parent component needs to pass three parameters
    • Whether to preview, is to perform preview operation switch
    • Preview index, initially, that is, the picture that is seen after the preview component is instantiated
    • preview array, including all img
props: {
    
    
   isPrview: {
    
    
     type: Boolean,
   }, // 是否在预览(也是触发预览的开关)
   previewIndex: {
    
    
     type: Number,
   }, // 预览索引
   previewImg: {
    
    
     type: Array,
   }, // 预览img
 },
  • Monitor isPrview, when it is true, perform preview initialization
watch: {
    
    
    isPrview(val) {
    
    
      if (val === true) this.initPhotoSwiper();
    },
  },
  • Preview initialization
initPhotoSwiper() {
    
    
   /**
    * 结构出来DOM元素
    */
   const {
    
     pswp } = this.$refs;
   const options = {
    
    
     index: this.previewIndex, // 初始化预览索引,也就是显示数组中第几张图片
   };
   this.gallery = new PhotoSwiper(pswp, UI, this.previewImg, options);
   // 实例化
   this.gallery.init();
   /**
    * 关闭按钮
    * 过滤调没选中的
    */
   this.gallery.listen('close', () => {
    
    
     const info = this.previewImg.filter((item) => item.completed).map((item) => ({
    
    
       src: item.src,
       id: item.id,
     }));
     console.log(info);
     this.$emit('changImageUrl', info);
   });
   /**
    * 点击下一张时获取对应状态
    */
   this.gallery.listen('beforeChange', () => {
    
    
     /**
      * this.gallery.getCurrentIndex() 会拿到当前图片索引
      * @type {*|boolean}
      */
     this.isactive = this.previewImg[this.gallery.getCurrentIndex()].completed;
   });
 },
  • When the custom button is clicked, toggle the selected state
checkboxClick() {
    
    
	/**
    * 切换是否选中状态
    */
   // eslint-disable-next-line max-len,vue/no-mutating-props
   this.previewImg[this.gallery.getCurrentIndex()].completed = !this.previewImg[this.gallery.getCurrentIndex()].completed;
   this.isactive = this.previewImg[this.gallery.getCurrentIndex()].completed;
 },

  • When the preview is closed, the array is filtered according to the completed (true is selected) of each picture in the array

Parent component HomeView.vue

  • After uploading the image, reading the file is an asynchronous operation, which needs to be processed synchronously through Promise
syncFile(file) {
    
    
   return new Promise((resolve, reject) => {
    
    
     const reader = new FileReader();

     reader.readAsDataURL(file);

     reader.onload = function (e) {
    
    
       resolve(e);
     };
     reader.onerror = () => {
    
    
       reject();
     };
   });
 },
  • After reading the file, temporarily save it in the imageUrl array, and enable the cropper to crop the image
async imgChange(e) {
    
    
  const {
    
     files } = e.target;
  for (let i = 0; i < files.length; i++) {
    
    
    // const reader = new FileReader();
    // reader.readAsDataURL(files[i]);
    // console.log(files[i]);
    // eslint-disable-next-line no-await-in-loop
    const rederUrl = await this.syncFile(files[i]);
    console.log(rederUrl);
    if (this.imageUrl.length === 0) {
    
    
      this.imageUrl.push({
    
    
        id: rederUrl.loaded,
        url: rederUrl.target.result,
      });
    } else {
    
    
      const status = this.imageUrl.some((item) => item.id === rederUrl.loaded);
      if (!status) {
    
    
        this.imageUrl.push({
    
    
          url: rederUrl.target.result,
          id: rederUrl.loaded,
        });
      }
    }
  }
  // 准备裁剪
  this.showCropper = true;
},
  • Because cropperjs does not support gif cropping, it is necessary to add whether it is a gif image in the information, and perform different cropping operations
// 拿到图片后缀名
const result = files[i].name.split('.')[1];
if (result === 'gif') {
    
    
  this.isGif = true;
} else {
    
    
  this.isGif = false;
}
  • The cropped image is saved in perImg, and if there is a file in the perImg array, it will be rendered to the page to provide a thumbnail preview
 imgCropped(data) {
    
    
   this.perImg.push({
    
    
     src: data.src,
     id: data.id,
   });
 },
  • When the picture is clicked, the index of the clicked picture is passed in, and the data structure of the picture is processed, and then the preview component is invoked
this.$refs.img.forEach((item, index) => {
    
    
    this.perImg[index].w = item.offsetWidth;
     this.perImg[index].h = item.offsetHeight;
     this.perImg[index].completed = true;
   });
   this.previewIndex = index;
   this.isPrview = true;
 })
  • It is possible to cancel the image operation during preview, so after closing the preview, reassign perImg
 changImageUrl(data) {
    
    
  this.perImg = data;
    this.isPrview = false;
}

Guess you like

Origin blog.csdn.net/weixin_64925940/article/details/125882628
Recommended