rush:js;">
props: {
img: String,//url或dataUrl
clipperImgWidth: {
type: Number,default: 500
},clipperImgHeight: {
type: Number,default: 200
}
}
rush:xhtml;">
三、初始化canvas
canvas绘制的图片在hdpi显示屏上会出现模糊,具体原因这里不作分析,可以参考下这里。我这里的做法是让canvas的width与height为其css width/height的devicePixelRatio倍,以及调用canvas api时所传入的参数都要乘以window.devicePixelRatio。最后还要记录一下两个canvas坐标原点的x,y差值(originXDiff与originYDiff)。如下
rush:js;">
_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 < 0 || clipperWidth > clipperClientRect.width) {
clipperWidth = 250
}
if (clipperHeight < 0 || 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;
}
加载图片比较简单,首先是创建一个Image对象并监听器onload事件(因为加载的图片有可能是跨域的,因此要设置其crossOrigin属性为Anonymous,然后服务器上要设置Access-Control-Allow-Origin响应头)。加载的图片如果宽高大于容器的宽高,要对其进行缩小处理。最后垂直水平居中显示()(这里注意的是要保存图片绘制前的宽高值,因为日后缩放图片是以该值为基础再乘以缩放倍率,这里取imgStartWidth,imgStartHeight)如下
{
$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._loadImg();
};
this.$emit('beforeLoad');
this.imgLoading = true;
this.imgLoaded = false;
$img.src = this.img;
$img.crossOrigin = 'Anonymous'; //因为canvas toDataUrl不能操作未经允许的跨域
图片,这需要服务器设置Access-Control-Allow-Origin头
$img.addEventListener('load',false);
$img.addEventListener('error',false);
}
_initImg(w,h) {
let eW = null,eH = null,maxW = this.cWidth,maxH = this.cHeight - this.actionBarHeight;
//如果
图片的宽高都少于容器的宽高,则不做处理
if (w <= maxW && h <= maxH) {
eW = w;
eH = h;
} else if (w > maxW && h <= maxH) {
eW = maxW;
eH = parseInt(h / w * maxW);
} else if (w <= maxW && h > 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);
}
}
下面的_drawImage有四个参数,分别是图片对应cCanvas的x,y坐标以及图片目前的宽高w,h。函数首先会清空两个canvas的内容,方法是重新设置canvas的宽高。然后更新组件实例中对应的值,最后再调用两个canvas的drawImage去绘制图片。对于pCanvas来说,其绘制的图片坐标值为x,y减去对应的originXDiff与originYDiff(其实相当于切换坐标系显示而已,因此只需要减去两个坐标系原点的x,y差值即可)。看看代码
rush:js;">
_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(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;
}
移动图片实现非常简单,首先给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来更新图片。
看看代码吧
{
if (!this.imgLoaded) {
return;
}
let finger = e.
touches[0];
scx = finger.pageX;
scy = finger.pageY;
iX = this.imgX;
iY = this.imgY;
},false);
$gesture.addEventListener('touchmove',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);
}
七、缩放图片(这里不作特别说明的坐标都是相对于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。
坚持...继续看看代码吧
{
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);
figure
distance = this._poin
tdistance(f1x,f1y,f2x,f2y);
fingers[finger1.identifier] = finger1;
fingers[finger2.identifier] = finger2;
//判断变换中点是否在
图片中,如果不是则去离
图片最近的点
if (scx < this.imgX) {
scx = this.imgX;
}
if (scx > this.imgX + this.imgCurrentWidth) {
scx = this.imgX + this.imgCurrentHeight;
}
if (scy < this.imgY) {
scy = this.imgY;
}
if (scy > this.imgY + this.imgCurrentHeight) {
scy = this.imgY + this.imgCurrentHeight;
}
}
},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,this.imgCurrentHeight);
} else if (e.
touches.length === 2) {
let finger1 = e.
touches[0],f2y = finger2.pageY -
cclientRect.top,new
figure
distance = this._poin
tdistance(f1x,f2y),scale = this.imgScale + parseFloat(((new
figure
distance -
figure
distance) / this.imgScaleStep).toFixed(1));
fingers[finger1.identifier] = finger1;
fingers[finger2.identifier] = finger2;
if (scale !== pinchScale) {
//目前缩放的最小比例是1,最大是5
if (scale < this.imgMinScale) {
scale = this.imgMinScale;
} else if (scale > this.imgMaxScale) {
scale = this.imgMaxScale;
}
pinchScale = scale;
this._scale(scx,scy,scale);
}
}
},false);
$gesture.addEventListener('touchend',e => {
if (!this.imgLoaded) {
return;
}
this.imgScale = pinchScale;
//从finger
删除已经离开的手指
let
touches = Array.prototype.slice.call(e.changed
touches,0);
touches.forEach(item => {
delete fingers[item.identifier];
});
//迭代fingers,如果存在finger则更新scx,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.left) {
x = pClientRect.left + pClientRect.width - this.imgCurrentWidth;
}
if (y > 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,this.imgCurrentHeight);
}
});
},_scale(x,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);
},_poin
tdistance(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为移动图片做初始化准备。
这里很简单,就调用pCanvas的toDataURL方法就可以了
rush:js;">
_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);
}
总结
上面只是列出了一些自己认为比较关键的点, 如果有兴趣的,可以到我的看看。
以上所述是小编给大家介绍的基于Vue的移动端图片裁剪组件功能。编程之家 jb51.cc 收集整理的教程希望能对你有所帮助,如果觉得编程之家不错,可分享给好友!感谢支持。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。