效果
源码地址
cropperjs对图片进行裁剪操作
地址cropperjs
实现思路
- 拿到父组件传递过来要裁剪的数组
- mounted阶段对当前图片类型进行判断是否是gif图片,并执行不同操作 组件进行实例化,并保存在数据中
croppering(){// 是否是gif图片if(this.imageUrl[this.index].isGif){this.gifInit();}else{this.init();}},
- 普通图片操作
init(){this.myCropper =newCropper(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 =newUint8Array(n);while(n--){
u8arr[n]= bstr.charCodeAt(n);}returnnewFile([u8arr], filename,{type: mime });},// 将canvas转换成file对象convertCanvasToImage(canvas, filename){returnthis.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 =newUint8Array(rawLength);for(let i =0; i < rawLength; i +=1){
uInt8Array[i]= raw.charCodeAt(i);}returnnewBlob([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 =newSuperGif({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 =newCropper(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合并asyncmergeGif(){// const width=300;// const height=300;const gif =newGIF({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-loopconst imgImage =awaitthis.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 =awaitthis.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-expressionsthis.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){returnnewPromise((resolve)=>{const imgImage =newImage();
imgImage.src =this.eachGif[i -1];
document.body.appendChild(imgImage);// console.log(imgImage);
imgImage.onload=()=>{resolve(imgImage);
imgImage.parentNode.removeChild(imgImage);};});},blobToBase64(blob){returnnewPromise((resolve, reject)=>{const fileReader =newFileReader();
fileReader.onload=(e)=>{resolve(e.target.result);};// readAsDataURL
fileReader.readAsDataURL(blob);
fileReader.onerror=()=>{reject(newError('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 =newPhotoSwiper(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-propsthis.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){returnnewPromise((resolve, reject)=>{const reader =newFileReader();
reader.readAsDataURL(file);
reader.onload=function(e){resolve(e);};
reader.onerror=()=>{reject();};});},
- 读取文件后暂时保存到imageUrl数组中,并启用裁剪器对图片进行裁剪操作
asyncimgChange(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-loopconst rederUrl =awaitthis.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;}
本文转载自: https://blog.csdn.net/weixin_64925940/article/details/125882628
版权归原作者 鹏程933 所有, 如有侵权,请联系我们删除。
版权归原作者 鹏程933 所有, 如有侵权,请联系我们删除。