多点触控交互
触摸事件接口支持特定于应用程序的单点和多点触控交互。但是,这些接口对于程序员来说可能有点棘手,因为触摸事件与其他 DOM 输入事件(例如 鼠标事件)非常不同。本指南中描述的应用程序展示了如何将触摸事件用于简单的单点和多点触控交互,这是构建特定于应用程序的手势所需的基础知识。
此应用程序的实时版本可在 GitHub 上找到。该 源代码可在 GitHub 上获得,并欢迎提交拉取请求和 错误报告。
示例
此示例演示了如何使用 touchstart
、touchmove
、touchcancel
和 touchend
触摸事件来执行以下手势:单点触控、双点(同时)触控、多于两点的同时触控、单指滑动和双指移动/捏合/滑动。
定义触摸目标
该应用程序使用 <div>
元素来表示四个触摸区域。
<style>
div {
margin: 0em;
padding: 2em;
}
#target1 {
background: white;
border: 1px solid black;
}
#target2 {
background: white;
border: 1px solid black;
}
#target3 {
background: white;
border: 1px solid black;
}
#target4 {
background: white;
border: 1px solid black;
}
</style>
全局状态
tpCache
用于缓存触摸点,以便在触发事件之外进行处理。
// Log events flag
let logEvents = false;
// Touch Point cache
const tpCache = [];
注册事件处理程序
为所有四种触摸事件类型注册了事件处理程序。 touchend
和 touchcancel
事件类型使用相同的处理程序。
function set_handlers(name) {
// Install event handlers for the given element
const el = document.getElementById(name);
el.ontouchstart = start_handler;
el.ontouchmove = move_handler;
// Use same handler for touchcancel and touchend
el.ontouchcancel = end_handler;
el.ontouchend = end_handler;
}
function init() {
set_handlers("target1");
set_handlers("target2");
set_handlers("target3");
set_handlers("target4");
}
移动/捏合/缩放处理程序
此函数为双指水平移动/捏合/缩放处理提供了非常基本的支持。代码不包含错误处理或垂直移动。请注意,捏合和缩放移动检测的阈值是特定于应用程序(以及设备)的。
// This is a very basic 2-touch move/pinch/zoom handler that does not include
// error handling, only handles horizontal moves, etc.
function handle_pinch_zoom(ev) {
if (ev.targetTouches.length === 2 && ev.changedTouches.length === 2) {
// Check if the two target touches are the same ones that started
// the 2-touch
const point1 = tpCache.findLastIndex(
(tp) => tp.identifier === ev.targetTouches[0].identifier,
);
const point2 = tpCache.findLastIndex(
(tp) => tp.identifier === ev.targetTouches[1].identifier,
);
if (point1 >= 0 && point2 >= 0) {
// Calculate the difference between the start and move coordinates
const diff1 = Math.abs(
tpCache[point1].clientX - ev.targetTouches[0].clientX,
);
const diff2 = Math.abs(
tpCache[point2].clientX - ev.targetTouches[1].clientX,
);
// This threshold is device dependent as well as application specific
const PINCH_THRESHOLD = ev.target.clientWidth / 10;
if (diff1 >= PINCH_THRESHOLD && diff2 >= PINCH_THRESHOLD)
ev.target.style.background = "green";
} else {
// empty tpCache
tpCache.length = 0;
}
}
}
触摸开始处理程序
touchstart
事件处理程序缓存触摸点以支持双指手势。它还调用 preventDefault()
以阻止浏览器应用进一步的事件处理(例如,鼠标事件模拟)。
function start_handler(ev) {
// If the user makes simultaneous touches, the browser will fire a
// separate touchstart event for each touch point. Thus if there are
// three simultaneous touches, the first touchstart event will have
// targetTouches length of one, the second event will have a length
// of two, and so on.
ev.preventDefault();
// Cache the touch points for later processing of 2-touch pinch/zoom
if (ev.targetTouches.length === 2) {
for (let i = 0; i < ev.targetTouches.length; i++) {
tpCache.push(ev.targetTouches[i]);
}
}
if (logEvents) log("touchStart", ev, true);
update_background(ev);
}
触摸移动处理程序
touchmove
处理程序出于上述相同原因调用 preventDefault()
,并调用捏合/缩放处理程序。
function move_handler(ev) {
// Note: if the user makes more than one "simultaneous" touches, most browsers
// fire at least one touchmove event and some will fire several touchmoves.
// Consequently, an application might want to "ignore" some touchmoves.
//
// This function sets the target element's border to "dashed" to visually
// indicate the target received a move event.
//
ev.preventDefault();
if (logEvents) log("touchMove", ev, false);
// To avoid too much color flashing many touchmove events are started,
// don't update the background if two touch points are active
if (!(ev.touches.length === 2 && ev.targetTouches.length === 2))
update_background(ev);
// Set the target element's border to dashed to give a clear visual
// indication the element received a move event.
ev.target.style.border = "dashed";
// Check this event for 2-touch Move/Pinch/Zoom gesture
handle_pinch_zoom(ev);
}
触摸结束处理程序
touchend
处理程序将事件目标的背景颜色恢复为其原始颜色。
function end_handler(ev) {
ev.preventDefault();
if (logEvents) log(ev.type, ev, false);
if (ev.targetTouches.length === 0) {
// Restore background and border to original values
ev.target.style.background = "white";
ev.target.style.border = "1px solid black";
}
}
应用程序 UI
该应用程序使用 <div>
元素作为触摸区域,并提供按钮来启用日志记录和清除日志。
<div id="target1">Tap, Hold or Swipe me 1</div>
<div id="target2">Tap, Hold or Swipe me 2</div>
<div id="target3">Tap, Hold or Swipe me 3</div>
<div id="target4">Tap, Hold or Swipe me 4</div>
<!-- UI for logging/debugging -->
<button id="log" onclick="enableLog(event);">Start/Stop event logging</button>
<button id="clearlog" onclick="clearLog(event);">Clear the log</button>
<p></p>
<output></output>
其他函数
这些函数支持应用程序,但与事件流没有直接关系。
更新背景颜色
触摸区域的背景颜色将按如下方式更改:无触摸为白色
;单点触控为黄色
;双点同时触控为粉色
,三点或更多点同时触控为浅蓝色
。有关检测双指移动/捏合/缩放时背景颜色更改的信息,请参阅 触摸移动处理程序。
function update_background(ev) {
// Change background color based on the number simultaneous touches
// in the event's targetTouches list:
// yellow - one tap (or hold)
// pink - two taps
// lightblue - more than two taps
switch (ev.targetTouches.length) {
case 1:
// Single tap`
ev.target.style.background = "yellow";
break;
case 2:
// Two simultaneous touches
ev.target.style.background = "pink";
break;
default:
// More than two simultaneous touches
ev.target.style.background = "lightblue";
}
}
事件日志记录
这些函数用于将事件活动记录到应用程序窗口,以支持调试和了解事件流。
function enableLog(ev) {
logEvents = !logEvents;
}
function log(name, ev, printTargetIds) {
const o = document.getElementsByTagName("output")[0];
let s =
`${name}: touches = ${ev.touches.length} ; ` +
`targetTouches = ${ev.targetTouches.length} ; ` +
`changedTouches = ${ev.changedTouches.length}`;
o.innerText += `${s}\n`;
if (printTargetIds) {
s = "";
for (let i = 0; i < ev.targetTouches.length; i++) {
s += `... id = ${ev.targetTouches[i].identifier}\n`;
}
o.innerText += s;
}
}
function clearLog(event) {
const o = document.getElementsByTagName("output")[0];
o.textContent = "";
}