• 移动端写文章,表情,图片裁剪(gif裁剪),图片预览


    效果

    请添加图片描述

    源码地址

    cropperjs对图片进行裁剪操作

    地址cropperjs

    实现思路

    • 拿到父组件传递过来要裁剪的数组
    • mounted阶段对当前图片类型进行判断是否是gif图片,并执行不同操作 组件进行实例化,并保存在数据中
    croppering() {
      // 是否是gif图片
      if (this.imageUrl[this.index].isGif) {
        this.gifInit();
      } else {
        this.init();
      }
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 普通图片操作
    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,
          });
        },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 当点击next时,将当前图片进行裁剪输出,并传递给父组件,并判断是否还有需要裁剪图片
    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');
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 由于cropperjs不支持gif裁剪,所以需要引入libgif.js(拆分gif图片)和gif.js(合并gif图片),然后像普通图片进行裁剪操作
    • gif图片裁剪初始化
    gifInit() {
     this.pre_load_gif(this.imageUrl[this.index].url);
    },
    
    • 1
    • 2
    • 3
    • libgif.js拆分gif,并将图片保存与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();
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 按照之前思路对每一帧图片进行裁剪,图片太多,自动裁剪
    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();
      }
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 合并gif
    // 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'));
         };
       });
     },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 点击关闭执行关闭裁剪器
    goBack() {
        // 关闭剪裁器
         this.$emit('closeCropper');
     },
    
    • 1
    • 2
    • 3
    • 4

    photoswipe实现对图片进行预览操作

    地址photoswipe

    实现思路

    • 需要父组件传递三个参数
      • 是否在预览,是执行预览操作开关
      • 预览索引,初始时,也就是预览组件实例化后看到的时那一张图片
      • 预览的数组,包含所有的img
    props: {
       isPrview: {
         type: Boolean,
       }, // 是否在预览(也是触发预览的开关)
       previewIndex: {
         type: Number,
       }, // 预览索引
       previewImg: {
         type: Array,
       }, // 预览img
     },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 监听isPrview,为true时,执行预览初始化
    watch: {
        isPrview(val) {
          if (val === true) this.initPhotoSwiper();
        },
      },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 预览初始化
    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;
       });
     },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 当点击自定义按钮时,切换选中状态
    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;
     },
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 关闭预览时,根据数组中每个图片的completed(true为选中)对数组进行过滤操作

    父组件HomeView.vue

    • 上传图片后,读取文件是异步操作,需要通过Promise进行同步处理
    syncFile(file) {
       return new Promise((resolve, reject) => {
         const reader = new FileReader();
    
         reader.readAsDataURL(file);
    
         reader.onload = function (e) {
           resolve(e);
         };
         reader.onerror = () => {
           reject();
         };
       });
     },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 读取文件后暂时保存到imageUrl数组中,并启用裁剪器对图片进行裁剪操作
    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;
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 因为cropperjs不支持gif裁剪,所以要在信息中加入是否是gif图片,并执行不同裁剪操作
    // 拿到图片后缀名
    const result = files[i].name.split('.')[1];
    if (result === 'gif') {
      this.isGif = true;
    } else {
      this.isGif = false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 裁剪后的图片保存于perImg,当perImg数组中有文件的话,将其渲染到页面上提供小图预览
     imgCropped(data) {
       this.perImg.push({
         src: data.src,
         id: data.id,
       });
     },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 当点击图片,传入点击图片索引,并对图片的数据结构进行处理,再调起预览组件
    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;
     })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 在预览时可能执行取消图片操作,所以在关闭预览,对perImg重新赋值
     changImageUrl(data) {
      this.perImg = data;
        this.isPrview = false;
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    云原生底座之上,顺丰智慧供应链领跑的秘密
    JAVA-读取excel转成html 将excel表格转换为HTML文件格式 转成前端表格样式
    Spring中的循环依赖解决方案
    【设计模式】建造者模式
    Kotlin设计模式:深入理解桥接模式
    (总目录)springboot - 实现zip文件上传并对zip文件解压, 包含上传oss
    交互设计之五要素
    记一次victoriaMetrics代理性能优化问题
    Tuxera NTFS2023破解版苹果电脑磁盘读写工具
    Java web基础知识
  • 原文地址:https://blog.csdn.net/weixin_64925940/article/details/125882628