捏合縮放手勢

為應用程式新增手勢可以顯著改善使用者體驗。手勢有很多種,從簡單的單點觸控滑動手勢到更復雜的多點觸控旋轉手勢,其中觸點(也稱為指標)朝不同方向移動。

本示例演示瞭如何檢測捏合/縮放手勢,該手勢使用 pointer events 來檢測使用者是將兩個指標移近還是移遠。

此應用程式的即時版本可在 GitHub 上獲得。原始碼可在 GitHub 上找到;歡迎提交 pull requestsbug 報告

示例

在此示例中,您可以使用 pointer events 同時檢測任何型別的兩個指向裝置,包括手指、滑鼠和筆。捏合(縮小)手勢(兩個指標相互靠近)會將目標元素的背景顏色更改為 lightblue。捏出(放大)手勢(兩個指標相互遠離)會將目標元素的背景顏色更改為 pink

定義觸控目標

應用程式使用 <div> 來定義指標的目標區域。

css
div {
  margin: 0em;
  padding: 2em;
}
#target {
  background: white;
  border: 1px solid black;
}

全域性狀態

支援雙指標手勢需要在各種事件階段保留指標的事件狀態。此應用程式使用兩個全域性變數來快取事件狀態。

js
// Global vars to cache event state
const evCache = [];
let prevDiff = -1;

註冊事件處理程式

為以下指標事件註冊了事件處理程式: pointerdownpointermovepointerup。對於 pointercancelpointeroutpointerleave 事件,使用了 pointerup 的處理程式,因為這四個事件在此應用程式中具有相同的語義。

js
// 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 事件。在此應用程式中,必須快取事件狀態,以防此按下事件是雙指標捏合/縮放手勢的一部分。

js
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,以提供清晰的視覺指示,表明元素已收到移動事件。

js
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 事件。發生這種情況時,事件將從事件快取中移除,並且目標元素的背景顏色和邊框將恢復到其原始值。

在此應用程式中,此處理程式也用於 pointercancelpointerleavepointerout 事件。

js
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> 元素。

html
<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>
css
body {
  touch-action: none; /* Prevent default touch behavior */
}

雜項函式

這些函式支援應用程式,但與事件流程沒有直接關係。

快取管理

此函式有助於管理全域性事件快取 evCache

js
function removeEvent(ev) {
  // Remove this event from the target's cache
  const index = evCache.findIndex(
    (cachedEv) => cachedEv.pointerId === ev.pointerId,
  );
  evCache.splice(index, 1);
}

事件日誌記錄

這些函式用於將事件活動傳送到應用程式視窗(以支援除錯和了解事件流程)。

js
// 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 = "";
}

另見