捏合缩放手势
为应用程序添加手势可以显著改善用户体验。手势有很多种,从简单的单点触摸滑动手势到更复杂的多点触摸旋转手势,其中触点(也称为指针)朝不同方向移动。
本示例演示了如何检测捏合/缩放手势,该手势使用 pointer events 来检测用户是将两个指针移近还是移远。
此应用程序的实时版本可在 GitHub 上获得。源代码可在 GitHub 上找到;欢迎提交 pull requests 和 bug 报告。
示例
在此示例中,您可以使用 pointer events 同时检测任何类型的两个指向设备,包括手指、鼠标和笔。捏合(缩小)手势(两个指针相互靠近)会将目标元素的背景颜色更改为 lightblue。捏出(放大)手势(两个指针相互远离)会将目标元素的背景颜色更改为 pink。
定义触摸目标
应用程序使用 <div> 来定义指针的目标区域。
div {
margin: 0em;
padding: 2em;
}
#target {
background: white;
border: 1px solid black;
}
全局状态
支持双指针手势需要在各种事件阶段保留指针的事件状态。此应用程序使用两个全局变量来缓存事件状态。
// Global vars to cache event state
const evCache = [];
let prevDiff = -1;
注册事件处理程序
为以下指针事件注册了事件处理程序: pointerdown、pointermove 和 pointerup。对于 pointercancel、pointerout 和 pointerleave 事件,使用了 pointerup 的处理程序,因为这四个事件在此应用程序中具有相同的语义。
// Install event handlers for the pointer target
const el = document.getElementById("target");
el.onpointerdown = pointerdownHandler;
el.onpointermove = pointermoveHandler;
// Use same handler for pointer{up,cancel,out,leave} events since
// the semantics for these events - in this app - are the same.
el.onpointerup = pointerupHandler;
el.onpointercancel = pointerupHandler;
el.onpointerout = pointerupHandler;
el.onpointerleave = pointerupHandler;
指针按下
当指针(鼠标、笔/触控笔或触摸屏上的触摸点)接触到接触面时,会触发 pointerdown 事件。在此应用程序中,必须缓存事件状态,以防此按下事件是双指针捏合/缩放手势的一部分。
function pointerdownHandler(ev) {
// The pointerdown event signals the start of a touch interaction.
// This event is cached to support 2-finger gestures
evCache.push(ev);
log("pointerDown", ev);
}
指针移动
pointermove 事件处理程序检测用户是否正在调用双指针捏合/缩放手势。如果两个指针都已按下,并且指针之间的距离正在增加(表示捏出或放大),则元素的背景颜色将变为 pink;如果指针之间的距离正在减小(捏入或缩小),则背景颜色将变为 lightblue。在更复杂的应用程序中,捏入或捏出判断可用于应用特定于应用程序的语义。
处理此事件时,目标元素的边框将设置为 dashed,以提供清晰的视觉指示,表明元素已收到移动事件。
function pointermoveHandler(ev) {
// This function implements a 2-pointer horizontal pinch/zoom gesture.
//
// If the distance between the two pointers has increased (zoom in),
// the target element's background is changed to "pink" and if the
// distance is decreasing (zoom out), the color is changed to "lightblue".
//
// This function sets the target element's border to "dashed" to visually
// indicate the pointer's target received a move event.
log("pointerMove", ev);
ev.target.style.border = "dashed";
// Find this event in the cache and update its record with this event
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache[index] = ev;
// If two pointers are down, check for pinch gestures
if (evCache.length === 2) {
// Calculate the distance between the two pointers
const curDiff = Math.abs(evCache[0].clientX - evCache[1].clientX);
if (prevDiff > 0) {
if (curDiff > prevDiff) {
// The distance between the two pointers has increased
log("Pinch moving OUT -> Zoom in", ev);
ev.target.style.background = "pink";
}
if (curDiff < prevDiff) {
// The distance between the two pointers has decreased
log("Pinch moving IN -> Zoom out", ev);
ev.target.style.background = "lightblue";
}
}
// Cache the distance for the next move event
prevDiff = curDiff;
}
}
指针抬起
当指针从接触面抬起时,会触发 pointerup 事件。发生这种情况时,事件将从事件缓存中移除,并且目标元素的背景颜色和边框将恢复到其原始值。
在此应用程序中,此处理程序也用于 pointercancel、pointerleave 和 pointerout 事件。
function pointerupHandler(ev) {
log(ev.type, ev);
// Remove this pointer from the cache and reset the target's
// background and border
removeEvent(ev);
ev.target.style.background = "white";
ev.target.style.border = "1px solid black";
// If the number of pointers down is less than two then reset diff tracker
if (evCache.length < 2) {
prevDiff = -1;
}
}
应用程序 UI
该应用程序使用 <div> 元素作为触摸区域,并提供按钮来启用日志记录和清除日志。
为防止浏览器默认的触摸行为覆盖应用程序的指针处理,将 touch-action 属性应用于 <body> 元素。
<div id="target">
Touch and Hold with 2 pointers, then pinch in or out.<br />
The background color will change to pink if the pinch is opening (Zoom In) or
changes to lightblue if the pinch is closing (Zoom out).
</div>
<!-- UI for logging/debugging -->
<button id="log">Start/Stop event logging</button>
<button id="clear-log">Clear the log</button>
<p></p>
<output></output>
body {
touch-action: none; /* Prevent default touch behavior */
}
杂项函数
这些函数支持应用程序,但与事件流程没有直接关系。
缓存管理
此函数有助于管理全局事件缓存 evCache。
function removeEvent(ev) {
// Remove this event from the target's cache
const index = evCache.findIndex(
(cachedEv) => cachedEv.pointerId === ev.pointerId,
);
evCache.splice(index, 1);
}
事件日志记录
这些函数用于将事件活动发送到应用程序窗口(以支持调试和了解事件流程)。
// Log events flag
let logEvents = false;
document.getElementById("log").addEventListener("click", enableLog);
document.getElementById("clear-log").addEventListener("click", clearLog);
// Logging/debugging functions
function enableLog(ev) {
logEvents = !logEvents;
}
function log(prefix, ev) {
if (!logEvents) return;
const o = document.getElementsByTagName("output")[0];
o.innerText += `${prefix}:
pointerID = ${ev.pointerId}
pointerType = ${ev.pointerType}
isPrimary = ${ev.isPrimary}
`;
}
function clearLog(event) {
const o = document.getElementsByTagName("output")[0];
o.textContent = "";
}
另见
- Pointer Events now in Firefox Nightly;Mozilla Hacks;作者:Matt Brubeck 和 Jason Weathersby;2015 年 8 月 4 日
- jQuery Pointer Events Polyfill
- Gestures;Material Design