前端手势控制图片插件书写一(手势识别的数学原理)
1、前端图片处理需求场景
前端图片处理应用在很多方面,在做业务过程中我也遇到了以下问题用到前端图片处理插件。
头像上传
背景图片上传
信用卡的Diy卡面上传
H5贴纸及合成
2、手势控制的数学知识
如果想要做图片旋转放大和拖动操作,一定会涉及到移动端的手势识别和手势操作。
在移动端控制图片操作中涉及到以下几个手势:单指拖动,单指缩放,单指旋转,双指缩放,双指旋转这五种手势 。
下面来介绍下如果使用向量来识别手势。
下图是单指在图片上旋转缩放时的示意图。只需要算出bs向量和bs'向量的夹角和他们的模的比例即可,与双指不同的是,单指手势需要一个中心点的坐标作为base点,这样才能构造出一个向量。
下图是双指在图片上旋转缩放时的示意图。与单指同理。
所以我们需要使用式子来表示出向量的角度和模。
e.touches可以获取到touch事件的手指数量及手指坐标。
a、从touches对象中获取点。
Gesture. getPoint();
eg:getPoint(event,index){ return { x: Math.round(event.touches[index].pageX), y: Math.round(event.touches[index].pageY), } }
b、从点得到向量。
Gesture. getVector()
eg:getVector(p1,p2){ let x = Math.round(p1.x - p2.x); let y = Math.round(p1.y - p2.y); return { x ,y }; }
c、计算向量的模。
Gesture. getVectorLength()
eg: getVectorLength(vct){ return Math.sqrt(vct.x * vct.x + vct.y * vct.y) }
d、计算两个向量的夹角(向量的数量积)。
Gesture. getVectorAngle();
eg: getVectorAngle(vct1, vct2){ //首先判断方向 let direction = vct1.x*vct2.y - vct2.x*vct1.y>0?1:-1; // console.log('direction',direction) let len1 = this.getVectorLength(vct1); let len2 = this.getVectorLength(vct2); let mr = len1*len2; let dot; let r; if(mr === 0) return 0; dot = vct1.x*vct2.x + vct1.y*vct2.y; r = dot/mr; console.log(r) if(r>1){ r =1; } if(r<-1){ r=-1; } let angle = Math.acos(r)*direction*180/Math.PI; return angle; }
3、如何表示双指旋转(示例)
首先在touchstart事件开始时,获取到开始触摸时的双指坐标,计算出初始向量和向量的模
startPoint = gesture.getPoint(e, 0); secondPoint = gesture.getPoint(e, 1); vector1 = gesture.getVector(secondPoint, startPoint); // console.error(vector1) pinchStartLength = gesture.getVectorLength(vector1);
然后同理,在touchmove事件触发时计算出目前向量的坐标及向量的模。通过刚刚的公式来计算出两个向量的夹角。
最后在touchend结束的时候,双指会出现两个手指如果不同时离开屏幕的现象。这时要在touchstart事件中判断初始手指数量,
如果在touchmove的同时,手指数量突然小于初始数量时,及时return ,避免执行单指move的代码。
绑定手指事件的实例代码:
let that = this; target.addEventListener('touchstart', (e) => { e.stopPropagation() e.preventDefault() console.warn('start', e.touches) console.warn('target', e.currentTarget) this.fingers = e.touches.length; if (e.touches.length == 1) { this.startPoint = this.gesture.getPoint(e, 0); // console.log('startPoint', startPoint) //获取图片的初始位置。 that.getPreTransformMatrix(target) } else if (e.touches.length == 2) { this.startPoint = this.gesture.getPoint(e, 0); this.secondPoint = this.gesture.getPoint(e, 1); // console.log('startPoint', startPoint) // console.log('secondPoint', secondPoint) this.vector1 = this.gesture.getVector(this.secondPoint, this.startPoint); // console.error(vector1) this.pinchStartLength = this.gesture.getVectorLength(this.vector1); // console.log(pinchStartLength) that.getPreTransformMatrix(target) } }) target.addEventListener('touchmove', (e) => { let curFingers = e.touches.length; // alert(e.touches.length) if (curFingers < this.fingers) { // alert(fingers) return; } // console.warn('move', e) if (e.touches.length == 1) { this.currentPoint = this.gesture.getPoint(e, 0); let detla; detla = { deltaX: this.currentPoint.x - this.startPoint.x, deltaY: this.currentPoint.y - this.startPoint.y, } this.set(target, { x: detla.deltaX, y: detla.deltaY, scale: 1, rotate: 0, }) // this.set($('#div_bg_img'), this.limit('', $('body'), dragTrans)) } else if (e.touches.length == 2) { // touchmove中计算实时的双指向量模; let curPoint = this.gesture.getPoint(e, 0); let curSecPoint = this.gesture.getPoint(e, 1); let vector2 = this.gesture.getVector(curSecPoint, curPoint); let pinchLength = this.gesture.getVectorLength(vector2); let angle = this.gesture.getVectorAngle(this.vector1, vector2) // console.log('pinch', { scale: pinchLength / pinchStartLength }) // console.log('Rotate', angle) // console.log('vector1', vector1) // console.log('vector2', vector2) this.set(target, { x: 0, y: 0, scale: pinchLength / this.pinchStartLength, rotate: angle, }) // this.set($('#div_bg_img'), this.limit('', $('body'), dragTrans)) } }) target.addEventListener('touchend', (e) => { // console.log('dragTransEND', dragTrans) console.log('dragTransEND', e.touches.length) console.log(target.firstElementChild) let orientation = that.getPhotoOrientation(target.firstElementChild); that.drawImagePic(orientation) })
前端笔记0-0