<template> <div class="clip-img" :style="imgStyle"> <img :src="url" crossOrigin="anonymous" :style="imgStyle"> <canvas ref="canvas" :style="imgStyle" @mousedown="onmousedown" @mousemove="onmousemove"></canvas> </div> </template> <script> export default { name:"clip-image", props:{ max:{ // 缩放基准宽度或高度 type:Number, default:400 } }, data(){ return { img:null, url:null, imgInfo: null, // 压缩前的信息 imgCInfo:null, // 压缩后的信息 clipInfo:null, // 压缩前的信息 clipCinfo:null, // 压缩后的信息 ctx:null, // 画板 pos:{x:0,y:0}, lock: "", // 锁住一个方向 boundary:null } }, computed:{ imgStyle(){ let imgCInfo = this.imgCInfo; if(imgCInfo){ return { width: `${imgCInfo.w}px`, height: `${imgCInfo.h}px` } } return {height:"0px",height:"0px",display:"none"}; } }, async mounted(){ this.clear(); this.ctx = this.$refs.canvas.getContext("2d"); window.addEventListener("mouseup",this.onmouseup); }, methods:{ init(src,bound){ this.setSrc(src) this.setClip(bound); }, // 外部调用 async setSrc(src){ let img = await this.loadImage(src,{crossOrigin:"anonymous"}); this.img = img; this.setImg(img); }, clear(){ let obj = { img:null, url:null, imgInfo: null, // 压缩前的信息 imgCInfo:null, // 压缩后的信息 clipInfo:null, // 压缩前的信息 clipCinfo:null, // 压缩后的信息 ctx:null, // 画板 pos:{x:0,y:0}, lock: "", // 锁住一个方向 boundary:null } for(let i in obj){ this[i] = obj[i]; } }, loadImage(url,attrs){ this.url = url; let img = new Image(); img.src = url; attrs = attrs || {}; for(let i in attrs){ img[i] = attrs[i]; } return new Promise((resolve,reject)=>{ img.onload = function(){ resolve(img); }; img.onerror = reject; }); }, setImg(img){ this.img = img; this.imgInfo = { w:img.width, h:img.height }; // 压缩图的比例 let w,h,scale; if(img.width > img.height){ w = this.max; scale = w/img.width; h = scale*img.height; }else{ h = this.max; scale = (h/img.height); w = scale * img.width; } this.imgCInfo = { w, h, scale }; let canvas = this.$refs.canvas; canvas.width = w; canvas.height = h; this.setClip(); }, setClip(clipInfo){ if(clipInfo){ this.clipInfo = { w:clipInfo.width, h:clipInfo.height }; } if(this.imgCInfo && this.clipInfo){ this.compressClip(this.imgCInfo,this.clipInfo); this.fill(); } }, compressClip(imgCInfo,clipInfo){ // 压缩缩放 let w,h,scale; let imgR = imgCInfo.w/imgCInfo.h; let clipR = clipInfo.w / clipInfo.h; if(imgR > clipR){ // 图片的宽度偏大 h = imgCInfo.h; scale = h/clipInfo.h; w = scale * clipInfo.w; this.lock = "h"; }else{ // 图片的宽度偏小 w = imgCInfo.w; scale = w/clipInfo.w; h = scale * clipInfo.h; this.lock = "w"; } this.clipCinfo = { w, h, scale } this.boundary = { w:this.imgCInfo.w - this.clipCinfo.w, h:this.imgCInfo.h - this.clipCinfo.h }; this.pos = { x:0, y:0 }; }, onmouseup(){ this.mouse = null; }, onmousedown(e){ this.mouse = { x:e.offsetX, y:e.offsetY } }, onmousemove(e){ if(this.mouse){ let x = e.offsetX - this.mouse.x ; let y = e.offsetY - this.mouse.y; if(this.lock == "h"){ x = this.pos.x + x; if(x < 0){ x = 0; }else if(x > this.boundary.w){ x = this.boundary.w } this.pos.x = x; }else{ y = this.pos.y + y; if(y < 0){ y = 0; }else if(y > this.boundary.h){ y = this.boundary.h } this.pos.y = y; } this.mouse = { x:e.offsetX, y:e.offsetY } this.fill(); } }, fill(){ let {w,h} = this.clipCinfo; let {x,y} = this.pos; let clipctx = this.ctx; let imgCInfo = this.imgCInfo; clipctx.clearRect(0, 0, imgCInfo.w, imgCInfo.h); clipctx.beginPath(); clipctx.fillStyle = ‘rgba(0,0,0,0.6)‘; clipctx.strokeStyle = "green"; //遮罩层 clipctx.globalCompositeOperation = "source-over"; clipctx.fillRect(0, 0, imgCInfo.w, imgCInfo.h); //画框 clipctx.globalCompositeOperation = ‘destination-out‘; clipctx.fillRect(x, y, w, h); //描边 clipctx.globalCompositeOperation = "source-over"; clipctx.moveTo(x, y); clipctx.lineTo(x + w, y); clipctx.lineTo(x + w, y + h); clipctx.lineTo(x, y + h); clipctx.lineTo(x, y); clipctx.stroke(); clipctx.closePath(); }, exportBase(){ // 导出图片 base64 let pos = this.pos; let scale = this.imgCInfo.scale; let sx = pos.x / scale; let sy = pos.y / scale; let swidth = parseInt(this.clipCinfo.w / scale); let sheight = parseInt(this.clipCinfo.h / scale); let canvas = document.createElement("canvas"); canvas.width = swidth; canvas.height = sheight; let ctx = canvas.getContext("2d"); ctx.drawImage(this.img,sx,sy,this.imgInfo.w,this.imgInfo.h,0,0,this.imgInfo.w,this.imgInfo.h); return canvas.toDataURL("image/png"); }, dataURLtoFile(b64Data,filename){ filename = filename || "test.png"; let mime = "image/png"; var bstr = atob(b64Data.replace(/^data:image\/(png|jpeg|jpg);base64,/, ‘‘)); var n = bstr.length; var u8arr = new Uint8Array(n); while(n--){ u8arr[n] = bstr.charCodeAt(n); } // 转换成file对象 return new File([u8arr], filename, {type:mime}); // 转换成成blob对象 // return new Blob([u8arr],{type:mime}); // return blob; } }, destroyed(){ window.removeEventListener("mouseup",this.onmouseup); } } </script> <style lang="scss" scoped> .clip-img{ border: 1px solid red; margin: 20px auto; position: relative; height: 0; width: 0; overflow: hidden; canvas{ position: absolute; left: 0; top: 0; z-index: 1; cursor: move; } img{ position: relative; z-index: 0; } } </style>
使用:
<template> <div class="clip-img"> <clipImage ref="clipImage"></clipImage> <button @click="getImg">导出</button> <button @click="slide">切换</button> <img :src="src" v-if="src" class="result"> </div> </template> <script> import clipImage from "@/components/clip-image.vue"; export default { name:"clip-image", data(){ return { src:"" } }, components:{ clipImage }, mounted(){ // setSrc this.$refs.clipImage.init("xxx",{width:400,height:200}); }, methods:{ getImg(){ this.src = this.$refs.clipImage.exportBase(); console.log("截图成功") }, slide(){ this.$refs.clipImage.setSrc("xxx"); } } } </script> <style lang="scss" scoped> .clip-img{ .result{ max-width: 400px; } } </style>
原文:https://www.cnblogs.com/muamaker/p/12051295.html