하이브리드 애플리케이션이 풍부해짐에 따라 HTML5 엔지니어는 더 이상 단순히 데스크톱 경험을 모바일로 포팅하는 데 만족하지 않고 모바일 네이티브 애플리케이션의 인간화된 운영 경험을 탐내고 있습니다. 특히 네이티브 애플리케이션에 내재된 풍부한 제스처 시스템이 그렇습니다. HTML5는 기본 제스처 시스템을 제공하지 않지만 터치 이벤트에 대한 낮은 수준의 모니터링을 제공합니다. 이를 바탕으로 우리는 우리만의 제스처 라이브러리를 만들 수 있습니다.
일반적으로 사용되는 HTML5 제스처는 단일 지점 제스처와 두 지점 제스처의 두 가지 범주로 나눌 수 있습니다. 단일 지점 제스처에는 탭, 두 번 탭, 길게 탭, 스와이프 및 이동이 포함됩니다. 2점 제스처에는 핀치(확대/축소) 및 회전(회전)이 포함됩니다.
다음으로 이러한 동작을 감지하는 JavaScript 라이브러리를 구현하고 이 동작 라이브러리를 사용하여 멋진 대화형 효과를 만듭니다.
여기에서는 모바일 동작 감지에 대해 자세히 설명하지 않습니다. 요약하자면, touchmove 이벤트가 발생할 때마다 두 변위 지점 사이의 좌표 위치를 빼면 됩니다.
제스처 감지의 핵심은 touchstart, touchmove, touchend의 세 가지 이벤트를 사용하여 제스처를 분해하는 것입니다.
그럼 클릭 이벤트를 어떻게 분해할까요?
하나의 터치 포인트만으로 터치시작이 발생할 때 클릭 감지를 입력합니다. 클릭 이벤트는 한 손가락 동작으로 제한되기 때문입니다.
터치무브 이벤트가 발생하지 않거나 터치무브가 작은 범위에 있습니다(아래 그림 참조). touchmove를 작은 범위로 제한하는 것은 사용자에게 어느 정도의 중복성을 제공하는 것입니다. 왜냐하면 사용자의 손가락이 화면을 터치할 때 약간 움직이지 않을 것이라는 보장이 없기 때문입니다.
3.touchend는 touchstart 후 짧은 시간 내에 발생합니다(아래 그림 참조). 이 기간에 대한 임계값은 밀리초 수준이며, 이는 손가락이 화면에 접촉하는 시간을 제한하는 데 사용됩니다. 클릭 이벤트가 처음부터 끝까지 매우 빠르기 때문입니다.
위의 과정을 통해 탭 이벤트 모니터링 구현을 시작할 수 있습니다.
rree싱글 클릭과 마찬가지로 더블 탭 이벤트도 제스처를 정량적으로 분해해야 합니다.
더블클릭 이벤트는 한 손가락의 동작입니다. 따라서 터치스타트 시 현재 화면에 몇 개의 접점이 있는지 확인해야 합니다.
더블 클릭 이벤트에는 두 개의 독립적인 클릭 동작이 포함됩니다. 이상적으로는 두 번의 클릭이 모두 화면의 동일한 지점에 도달해야 합니다. 사용자에게 일정량의 중복 공간을 제공하기 위해 두 번의 클릭에 대한 좌표점 사이의 거리는 10픽셀 이내로 제한됩니다.
더블 클릭 이벤트는 기본적으로 두 번의 빠른 클릭입니다. 즉, 두 번의 클릭 사이의 시간이 매우 짧습니다. 특정 테스트 수량화를 통과한 후 두 번의 클릭 사이의 시간 간격을 300밀리초로 설정했습니다.
두 번 클릭 이벤트에서는 인접한 두 터치시작 이벤트 사이의 변위와 시간 간격을 감지합니다.
아아아아길게 누르는 것이 가장 쉬운 분해 동작입니다. 이를 다음과 같이 분해할 수 있습니다. 터치시작이 발생한 후 오랜 시간 내에 터치무브 또는 터치엔드 이벤트가 발생하지 않으면 길게 누르기 동작이 트리거됩니다.
길게 누르는 것은 손가락 동작이며 화면에 접점이 하나만 있는지 감지해야 합니다.
손가락이 공간에서 움직이면 길게 누르기 이벤트가 취소됩니다.
손가락이 화면에 800ms 이상 머물면 길게 누르기 동작이 실행됩니다.
손가락이 화면에 800ms 미만 머물면, 즉 터치시작이 발생한 후 800ms 이내에 터치엔드가 실행되면 길게 누르기 이벤트가 취소됩니다.
_getTime() { return new Date().getTime(); } _onTouchStart(e) { //记录touch开始的位置 this.startX = e.touches[0].pageX; this.startY = e.touches[0].pageY; if(e.touches.length > 1) { //多点监测 ... }else { //记录touch开始的时间 this.startTime = this._getTime(); } } _onTouchMove(e) { ... //记录手指移动的位置 this.moveX = e.touches[0].pageX; this.moveY = e.touches[0].pageY; ... } _onTouchEnd(e) { let timestamp = this._getTime(); if(this.moveX !== null && Math.abs(this.moveX - this.startX) > 10 || this.moveY !== null && Math.abs(this.moveY - this.startY) > 10) { ... }else { //手指移动的位移要小于10像素并且手指和屏幕的接触时间要短语500毫秒 if(timestamp - this.startTime < 500) { this._emitEvent('onTap') } } }
Zoom은 매우 흥미로운 동작입니다. 1세대 iPhone의 두 손가락 줌 사진을 기억해 보세요. 충격이야? 그럼에도 불구하고 확대/축소 제스처 감지는 비교적 간단합니다.
확대/축소는 화면에 두 개의 접점이 있는지 감지해야 하는 두 손가락 동작입니다.
스케일링 비율의 정량화는 아래 그림과 같이 두 스케일링 작업 사이의 거리 비율로 구합니다.
所以缩放的核心是获取两个接触点之间的直线距离。
//勾股定理 _getDistance(xLen,yLen) { return Math.sqrt(xLen * xLen + yLen * yLen); }
这里的xLen是两个接触点x坐标差的绝对值,yLen相应的就是y坐标差的绝对值。
_onTouchStart(e) { if(e.touches.length > 1) { let point1 = e.touches[0]; let point2 = e.touches[1]; let xLen = Math.abs(point2.pageX - point1.pageX); let yLen = Math.abs(point2.pageY - point1.pageY); this.touchDistance = this._getDistance(xLen, yLen); } else { ... } }
在_onTouchStart函数中获取并且保存 touchstart 发生时两个接触点之间的距离。
_onTouchMove(e) { if(e.touches.length > 1) { let xLen = Math.abs(e.touches[0].pageX - e.touches[1].pageX); let yLen = Math.abs(e.touches[1].pageY - e.touches[1].pageY); let touchDistance = this._getDistance(xLen,yLen); if(this.touchDistance) { let pinchScale = touchDistance / this.touchDistance; this._emitEvent('onPinch',{scale:pinchScale - this.previousPinchScale}); this.previousPinchScale = pinchScale; } }else { ... } }
旋转手势需要检测两个比较重要的值,一是旋转的角度,二是旋转的方向(顺时针或逆时针)。
其中旋转角度和方向的计算需要通过向量的计算来获取,本文不再展开。
首先,需要获取向量的旋转方向和角度。
//这两个方法属于向量计算,具体原理请阅读本文最后的参考文献 _getRotateDirection(vector1,vector2) { return vector1.x * vector2.y - vector2.x * vector1.y; } _getRotateAngle(vector1,vector2) { let direction = this._getRotateDirection(vector1,vector2); direction = direction > 0 ? -1 : 1; let len1 = this._getDistance(vector1.x,vector1.y); let len2 = this._getDistance(vector2.x,vector2.y); let mr = len1 * len2; if(mr === 0) return 0; let dot = vector1.x * vector2.x + vector1.y * vector2.y; let r = dot / mr; if(r > 1) r = 1; if(r < -1) r = -1; return Math.acos(r) * direction * 180 / Math.PI; }
然后,我们在手指发生移动时,调用获取旋转方向和角度的方法。
_onTouchStart(e) { ... if(e.touches.length > 1) { this.touchVector = { x: point2.pageX - this.startX, y: point2.pageY - this.startY }; } ... } _onTouchMove(e) { ... if(this.touchVector) { let vector = { x: e.touches[1].pageX - e.touches[0].pageX, y: e.touches[1].pageY - e.touches[0].pageY }; let angle = this._getRotateAngle(vector,this.touchVector); this._emitEvent('onRotate',{ angle }); this.touchVector.x = vector.x; this.touchVector.y = vector.y; } ... }
好了,我们的手势系统到这里就完成了。接下来要在实战中检验这套系统是否可靠,做一个简单的图片浏览器,支持图片缩放,旋转,移动,长按。
首先,做好DOM规划,和“之前”一样,我们的事件监听机制并不直接作用在图片上,而是作用在图片的父元素上。
然后,可以开始使用上面的手势检测系统了。
render() { return ( <Gestures onPinch={this.onPinch} onMove={this.onMove} onRotate={this.onRotate} onDoubleTap={this.onDoubleTap} onLongPress={this.onLongPress}> <p className="wrapper" > ![](//m.sbmmt.com/) </p> </Gestures> ); }
由于我们的手势系统检测的增量,因此不能直接把增量应用在对象上,而是需要把这些增量累加。以旋转为例:
onRotate(event) { //对增量进行累加 this.angle += event.angle this.setState({ angle:this.angle }); }
至此,我们的手势检测就完成了。
以上就是HTML5 手势检测原理和实现的内容,更多相关内容请关注PHP中文网(m.sbmmt.com)!