1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 使用Vue移动端如何实现图片裁剪组件

使用Vue移动端如何实现图片裁剪组件

时间:2019-01-03 02:40:09

相关推荐

使用Vue移动端如何实现图片裁剪组件

web前端|js教程

vue,移动端图片裁剪,vue,图片裁剪组件

web前端-js教程

这篇文章给大家介绍了基于Vue的移动端图片裁剪组件功能,因为移动端是用vue,所以就写成了一个vue组件,下面就说说自己的一些实现思路,需要的朋友可以参考下

网络预约挂号系统源码,炫龙 阿尔法 ubuntu,tomcat占用两g内存吗,钉钉爬虫逆向,php教程零基础入门书,网站seo实例lzw

最近项目上要做一个车牌识别的功能。本来以为很简单,只需要将图片扔给后台就可以了,但是经测试后识别率只有20-40%。因此产品建议拍摄图片后,可以对图片进行拖拽和缩放,然后裁剪车牌部分上传给后台来提高识别率。刚开始的话还是百度了一下看看有没有现成的组件,但是找来找去都没有找到一个合适的,还好这个功能不是很着急,因此自己周末就在家里研究一下。

红包营销网站源码,ubuntu启动终端窗口,修改tomcat进程用户名,上海梅园爬虫市场,php实训报告设计原则,美国seo查询lzw

Demo地址:https://vivialex.github.io/demo/imageClipper/index.html

文本源码软件,rk3399ubuntu,爬虫搜索图片原理,乌兰察布线上php软件开发,汝州seo公司lzw

下载地址:/vivialex/vue-imageClipper

因为移动端是用vue,所以就写成了一个vue组件,下面就说说自己的一些实现思路(本人技术有限,各位大神请体谅。另外展示的代码不一定是某个功能的完整代码),先看看效果:

A、组件的初始化参数

1、图片img(url或者base64 data-url)

2、截图的宽clipperImgWidth

3、截图的高clipperImgHeight

props: { img: String, //url或dataUrl clipperImgWidth: { type: Number, default: 500 }, clipperImgHeight: { type: Number, default: 200 }}

B、布局

在Z轴方向看主要是由4层组成。第1层是一个占满整个容器的canvas(称cCanvas);第2层是一个有透明度的遮罩层;第3层是裁剪的区域(示例图中的白色方框),里面包含一个与裁剪区域大小相等的canvas(称pCanvas);第4层是一个透明层gesture-mask,用作绑定touchstart,touchmove,touchend事件。其中两个canvas都会加载同一张图片,只是起始坐标不一样。为什么需要两个canvas?因为想做出当手指离开屏幕时,裁剪区域外的部分表面会有一个遮罩层的效果,这样能突出裁剪区域的内容。

C、初始化canvas

canvas绘制的图片在hdpi显示屏上会出现模糊,具体原因这里不作分析,可以参考下这里。我这里的做法是让canvas的width与height为其css width/height的devicePixelRatio倍,以及调用canvas api时所传入的参数都要乘以window.devicePixelRatio。最后还要记录一下两个canvas坐标原点的x, y差值(originXDiff与originYDiff)。如下

_ratio(size) { return parseInt(window.devicePixelRatio * size);},_initCanvas() { let $canvas = this.$refs.canvas, $pCanvas = this.$refs.pCanvas, clipperClientRect = this.$refs.clipper.getBoundingClientRect(), clipperWidth = parseInt(this.clipperImgWidth / window.devicePixelRatio), clipperHeight = parseInt(this.clipperImgHeight / window.devicePixelRatio); this.ctx = $canvas.getContext(2d); this.pCtx = $pCanvas.getContext(2d); //判断clipperWidth与clipperHeight有没有超过容器值 if (clipperWidth clipperClientRect.width) { clipperWidth = 250 } if (clipperHeight clipperClientRect.height) { clipperHeight = 100 } //因为canvas在手机上会被放大,因此里面的内容会模糊,这里根据手机的devicePixelRatio来放大canvas,然后再通过设置css来收缩,因此关于canvas的所有值或坐标都要乘以devicePixelRatio $canvas.style.width = clipperClientRect.width + px; $canvas.style.height = clipperClientRect.height + px; $canvas.width = this._ratio(clipperClientRect.width); $canvas.height = this._ratio(clipperClientRect.height); $pCanvas.style.width = clipperWidth + px; $pCanvas.style.height = clipperHeight + px; $pCanvas.width = this._ratio(clipperWidth); $pCanvas.height = this._ratio(clipperHeight); //计算两个canvas原点的x y差值 let cClientRect = $canvas.getBoundingClientRect(), pClientRect = $pCanvas.getBoundingClientRect(); this.originXDiff = pClientRect.left - cClientRect.left; this.originYDiff = pClientRect.top - cClientRect.top; this.cWidth = cClientRect.width; this.cHeight = cClientRect.height;}

D、加载图片

加载图片比较简单,首先是创建一个Image对象并监听器onload事件(因为加载的图片有可能是跨域的,因此要设置其crossOrigin属性为Anonymous,然后服务器上要设置Access-Control-Allow-Origin响应头)。加载的图片如果宽高大于容器的宽高,要对其进行缩小处理。最后垂直水平居中显示()(这里注意的是要保存图片绘制前的宽高值,因为日后缩放图片是以该值为基础再乘以缩放倍率,这里取imgStartWidth,imgStartHeight)如下

_loadImg() { if (this.imgLoading || this.loadImgQueue.length === 0) { return; } let img = this.loadImgQueue.shift(); if (!img) { return; } let $img = new Image(), onLoad = e => {$img.removeEventListener(load, onLoad, false);this.$img = $img;this.imgLoaded = true;this.imgLoading = false;this._initImg($img.width, $img.height);this.$emit(loadSuccess, e);this.$emit(loadComplete, e);this._loadImg(); }, onError = e => {$img.removeEventListener(error, onError, false);this.$img = $img = null;this.imgLoading = false;this.$emit(loadError, e);this.$emit(loadComplete, e);this._loadImg(); }; this.$emit(eforeLoad); this.imgLoading = true; this.imgLoaded = false; $img.src = this.img; $img.crossOrigin = Anonymous; //因为canvas toDataUrl不能操作未经允许的跨域图片,这需要服务器设置Access-Control-Allow-Origin头 $img.addEventListener(load, onLoad, false); $img.addEventListener(error, onError, false);}_initImg(w, h) { let eW = null, eH = null, maxW = this.cWidth, maxH = this.cHeight - this.actionBarHeight; //如果图片的宽高都少于容器的宽高,则不做处理 if (w <= maxW && h maxW && h <= maxH) { eW = maxW; eH = parseInt(h / w * maxW); } else if (w maxH) { eW = parseInt(w / h * maxH); eH = maxH; } else { //判断是横图还是竖图 if (h > w) {eW = parseInt(w / h * maxH);eH = maxH; } else {eW = maxW;eH = parseInt(h / w * maxW); } } if (eW <= maxW && eH <= maxH) { //记录其初始化的宽高,日后的缩放功能以此值为基础 this.imgStartWidth = eW; this.imgStartHeight = eH; this._drawImage((maxW - eW) / 2, (maxH - eH) / 2, eW, eH); } else { this._initImg(eW, eH); }}

E、绘制图片

下面的_drawImage有四个参数,分别是图片对应cCanvas的x,y坐标以及图片目前的宽高w,h。函数首先会清空两个canvas的内容,方法是重新设置canvas的宽高。然后更新组件实例中对应的值,最后再调用两个canvas的drawImage去绘制图片。对于pCanvas来说,其绘制的图片坐标值为x,y减去对应的originXDiff与originYDiff(其实相当于切换坐标系显示而已,因此只需要减去两个坐标系原点的x,y差值即可)。看看代码

_drawImage(x, y, w, h) { this._clearCanvas(); this.imgX = parseInt(x); this.imgY = parseInt(y); this.imgCurrentWidth = parseInt(w); this.imgCurrentHeight = parseInt(h); //更新canvas this.ctx.drawImage(this.$img, this._ratio(x), this._ratio(y), this._ratio(w), this._ratio(h)); //更新pCanvas,只需要减去两个canvas坐标原点对应的差值即可 this.pCtx.drawImage(this.$img, this._ratio(x - this.originXDiff), this._ratio(y - this.originYDiff), this._ratio(w), this._ratio(h));},_clearCanvas() { let $canvas = this.$refs.canvas, $pCanvas = this.$refs.pCanvas; $canvas.width = $canvas.width; $canvas.height = $canvas.height; $pCanvas.width = $pCanvas.width; $pCanvas.height = $pCanvas.height;}

F、移动图片

移动图片实现非常简单,首先给gesture-mask绑定touchstart,touchmove,touchend事件,下面分别介绍这三个事件的内容

首先定义四个变量scx, scy(手指的起始坐标),iX,iY(图片目前的坐标,相对于cCanvas)。

1、touchstart

方法很简单,就是获取touches[0]的pageX,pageY来更新scx与scy以及更新iX与iY

2、touchmove

获取touches[0]的pageX,声明变量f1x存放,移动后的x坐标等于iX + f1x – scx,y坐标同理,最后调用_drawImage来更新图片。

看看代码吧

_initEvent() { let $gesture = this.$refs.gesture, scx = 0, scy = 0; let iX = this.imgX, iY = this.imgY; $gesture.addEventListener( ouchstart, e => { if (!this.imgLoaded) {return; } let finger = e.touches[0];scx = finger.pageX;scy = finger.pageY;iX = this.imgX;iY = this.imgY; }, false); $gesture.addEventListener( ouchmove, e => { e.preventDefault(); if (!this.imgLoaded) {return; } let f1x = e.touches[0].pageX,f1y = e.touches[0].pageY;this._drawImage(iX + f1x - scx, iY + f1y - scy, this.imgCurrentWidth, this.imgCurrentHeight); }, false);}

G、缩放图片(这里不作特别说明的坐标都是相对于cCanvas坐标系)

绘制缩放后的图片无非需要4个参数,缩放后图片左上角的坐标以及宽高。求宽高相对好办,宽高等于imgStartWidth * 缩放比率与imgstartHeight * 缩放倍率(imgStartWidth ,imgstartHeight 上文第四节有提到)。接下来就是求缩放倍率的问题了,首先在touchstart事件上求取两手指间的距离d1;然后在touchmove事件上继续求取两手指间的距离d2,当前缩放倍率= 初始缩放倍率 + (d2-d1) / 步长(例如每60px算0.1),touchend事件上让初始缩放倍率=当前缩放倍率。

至于如何求取缩放后图片左上角的坐标值,在草稿纸上画来画去,画了很久……终于有点眉目。首先要找到一个缩放中心(这里做法是取双指的中点坐标,但是这个坐标必须要位于图片上,如果不在图片上,则取图片上离该中点坐标最近的点),然后存在下面这个等式

(缩放中心x坐标 – 缩放后图片左上角x坐标)/ 缩放后图片的宽度 = (缩放中心x坐标 – 缩放前图片左上角x坐标)/ 缩放前图片的宽度;(y坐标同理)

接下来看看下面这个例子(在visio找了很久都没有画坐标系的功能,所以只能手工画了)

绿色框是一张10*5的图片,蓝色框是宽高放大两倍后的图片20*10,根据上面的公式推算的x2 = sx – w2(sx – x1) / w1,y2 = sy – h2(sy – y1) / h1。

坚持…继续看看代码吧

_initEvent() { let $gesture = this.$refs.gesture, cClientRect = this.$refs.canvas.getBoundingClientRect(), scx = 0, //对于单手操作是移动的起点坐标,对于缩放是图片距离两手指的中点最近的图标。 scy = 0, fingers = {}; //记录当前有多少只手指在触控屏幕 //one finger let iX = this.imgX, iY = this.imgY; //two finger let figureDistance = 0, pinchScale = this.imgScale; $gesture.addEventListener( ouchstart, e => { if (!this.imgLoaded) {return; } if (e.touches.length === 1) {let finger = e.touches[0];scx = finger.pageX;scy = finger.pageY;iX = this.imgX;iY = this.imgY;fingers[finger.identifier] = finger; } else if (e.touches.length === 2) {let finger1 = e.touches[0], finger2 = e.touches[1], f1x = finger1.pageX - cClientRect.left, f1y = finger1.pageY - cClientRect.top, f2x = finger2.pageX - cClientRect.left, f2y = finger2.pageY - cClientRect.top;scx = parseInt((f1x + f2x) / 2);scy = parseInt((f1y + f2y) / 2);figureDistance = this._pointDistance(f1x, f1y, f2x, f2y);fingers[finger1.identifier] = finger1;fingers[finger2.identifier] = finger2;//判断变换中点是否在图片中,如果不是则去离图片最近的点if (scx this.imgX + this.imgCurrentWidth) { scx = this.imgX + this.imgCurrentHeight;}if (scy this.imgY + this.imgCurrentHeight) { scy = this.imgY + this.imgCurrentHeight;} } }, false); $gesture.addEventListener( ouchmove, e => { e.preventDefault(); if (!this.imgLoaded) {return; } this.maskShowTimer && clearTimeout(this.maskShowTimer); this.maskShow = false; if (e.touches.length === 1) {let f1x = e.touches[0].pageX, f1y = e.touches[0].pageY;this._drawImage(iX + f1x - scx, iY + f1y - scy, this.imgCurrentWidth, this.imgCurrentHeight); } else if (e.touches.length === 2) {let finger1 = e.touches[0], finger2 = e.touches[1], f1x = finger1.pageX - cClientRect.left, f1y = finger1.pageY - cClientRect.top, f2x = finger2.pageX - cClientRect.left, f2y = finger2.pageY - cClientRect.top, newFigureDistance = this._pointDistance(f1x, f1y, f2x, f2y), scale = this.imgScale + parseFloat(((newFigureDistance - figureDistance) / this.imgScaleStep).toFixed(1));fingers[finger1.identifier] = finger1;fingers[finger2.identifier] = finger2;if (scale !== pinchScale) { //目前缩放的最小比例是1,最大是5 if (scale this.imgMaxScale) {scale = this.imgMaxScale; } pinchScale = scale; this._scale(scx, scy, scale);} } }, false); $gesture.addEventListener( ouchend, e => { if (!this.imgLoaded) {return; } this.imgScale = pinchScale; //从finger删除已经离开的手指 let touches = Array.prototype.slice.call(e.changedTouches, 0); touches.forEach(item => {delete fingers[item.identifier]; }); //迭代fingers,如果存在finger则更新scx,scy,iX,iY,因为可能缩放后立即单指拖动 let i,fingerArr = []; for(i in fingers) {if (fingers.hasOwnProperty(i)) { fingerArr.push(fingers[i]);} } if (fingerArr.length > 0) {scx = fingerArr[0].pageX;scy = fingerArr[0].pageY;iX = this.imgX;iY = this.imgY; } else {this.maskShowTimer = setTimeout(() => { this.maskShow = true;}, 300); } //做边界值检测 let x = this.imgX,y = this.imgY,pClientRect = this.$refs.pCanvas.getBoundingClientRect(); if (x > pClientRect.left + pClientRect.width) {x = pClientRect.left } else if (x + this.imgCurrentWidth pClientRect.top + pClientRect.height) {y = pClientRect.top; } else if (y + this.imgCurrentHeight < pClientRect.top) {y = pClientRect.top + pClientRect.height - this.imgCurrentHeight; } if (this.imgX !== x || this.imgY !== y) {this._drawImage(x, y, this.imgCurrentWidth, this.imgCurrentHeight); } });},_scale(x, y, scale) { let newPicWidth = parseInt(this.imgStartWidth * scale), newPicHeight = parseInt(this.imgStartHeight * scale), newIX = parseInt(x - newPicWidth * (x - this.imgX) / this.imgCurrentWidth), newIY = parseInt(y - newPicHeight * (y - this.imgY) / this.imgCurrentHeight); this._drawImage(newIX, newIY, newPicWidth, newPicHeight);},_pointDistance(x1, y1, x2, y2) { return parseInt(Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));}

说明一下fingers是干嘛的,是用来记录当前有多少只手指在屏幕上触摸。可能会出现这种情况,双指缩放后,其中一只手指移出显示屏,而另外一个手指在显示屏上移动。针对这种情况,要在touchend事件上根据e.changedTouches来移除fingers里已经离开显示屏的finger,如果此时fingers里只剩下一个finger,则更新scx,scy,iX,iY为移动图片做初始化准备。

H、裁剪图片

这里很简单,就调用pCanvas的toDataURL方法就可以了

_clipper() { let imgData = null; try { imgData = this.$refs.pCanvas.toDataURL(); } catch (e) { console.error(请在response header加上Access-Control-Allow-Origin,否则canvas无法裁剪未经许可的跨域图片); } this.$emit(sure, imgData);}

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在Bootstrap框架里使用treeview如何实现动态加载数据

在Nginx中如何配置多站点vhost

在vue中如何实现跳转到之前页面

在express+mockjs中如何实现后台数据发送

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。