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

效果

请添加图片描述

源码地址

cropperjs对图片进行裁剪操作

地址cropperjs

实现思路

  • 拿到父组件传递过来要裁剪的数组
  • mounted阶段对当前图片类型进行判断是否是gif图片,并执行不同操作 组件进行实例化,并保存在数据中
croppering() {
    
    
  // 是否是gif图片
  if (this.imageUrl[this.index].isGif) {
    
    
    this.gifInit();
  } else {
    
    
    this.init();
  }
},
  • 普通图片操作
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,
      });
    },
  • 当点击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');
 }
  • 由于cropperjs不支持gif裁剪,所以需要引入libgif.js(拆分gif图片)和gif.js(合并gif图片),然后像普通图片进行裁剪操作
  • gif图片裁剪初始化
gifInit() {
    
    
 this.pre_load_gif(this.imageUrl[this.index].url);
},
  • 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();
},
  • 按照之前思路对每一帧图片进行裁剪,图片太多,自动裁剪
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();
  }
},
  • 合并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'));
     };
   });
 },
  • 点击关闭执行关闭裁剪器
goBack() {
    
    
    // 关闭剪裁器
     this.$emit('closeCropper');
 },

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

地址photoswipe

实现思路

  • 需要父组件传递三个参数
    • 是否在预览,是执行预览操作开关
    • 预览索引,初始时,也就是预览组件实例化后看到的时那一张图片
    • 预览的数组,包含所有的img
props: {
    
    
   isPrview: {
    
    
     type: Boolean,
   }, // 是否在预览(也是触发预览的开关)
   previewIndex: {
    
    
     type: Number,
   }, // 预览索引
   previewImg: {
    
    
     type: Array,
   }, // 预览img
 },
  • 监听isPrview,为true时,执行预览初始化
watch: {
    
    
    isPrview(val) {
    
    
      if (val === true) this.initPhotoSwiper();
    },
  },
  • 预览初始化
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;
   });
 },
  • 当点击自定义按钮时,切换选中状态
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;
 },

  • 关闭预览时,根据数组中每个图片的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();
     };
   });
 },
  • 读取文件后暂时保存到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;
},
  • 因为cropperjs不支持gif裁剪,所以要在信息中加入是否是gif图片,并执行不同裁剪操作
// 拿到图片后缀名
const result = files[i].name.split('.')[1];
if (result === 'gif') {
    
    
  this.isGif = true;
} else {
    
    
  this.isGif = false;
}
  • 裁剪后的图片保存于perImg,当perImg数组中有文件的话,将其渲染到页面上提供小图预览
 imgCropped(data) {
    
    
   this.perImg.push({
    
    
     src: data.src,
     id: data.id,
   });
 },
  • 当点击图片,传入点击图片索引,并对图片的数据结构进行处理,再调起预览组件
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;
 })
  • 在预览时可能执行取消图片操作,所以在关闭预览,对perImg重新赋值
 changImageUrl(data) {
    
    
  this.perImg = data;
    this.isPrview = false;
}

猜你喜欢

转载自blog.csdn.net/weixin_64925940/article/details/125882628
今日推荐