目錄

    • TL;DR
    • 1 基礎概念
      • 1.1 鼠標、觸控板
      • 1.2 點擊和手勢
    • 2 觸控板獨有功能以及推薦用法
      • 2.1 觸控板三指拖移
    • 3 鼠標觸控板開發
      • 3.1 wheel 小實驗
      • 3.2 實驗結論
        • 3.2.1 觸控板
        • 3.2.2 鼠標
      • 3.3 禁用觸控板雙指輕掃返回、智能縮放
      • 3.4 註意點
    • 參考連接

開發白板過程中,畫佈平移縮放操作是使用很頻繁且最基礎的交互功能。由於需求排期一直未實現該功能,導致白板開發使用過程中體驗差點意思,有一天我終於忍不瞭瞭,花瞭一個晚上研究瞭一下相關的內容。所以有瞭以下實踐。

一、在白板類繪圖產品中,平移縮放大部分都是依靠鼠標/觸控板操作完成的。這裡不可避免的要處理鼠標事件相關的代碼邏輯。針對不同設備需要兼容處理不同的邏輯,尤其在使用 mac 觸控板時左滑返回上一頁的操作很幹擾畫圖,會導致瀏覽器頁面後退,需要禁用該操作。

二、在我們日常工作生活中,幾乎每天都需要用鼠標或者觸控板。但是發現周圍大部分用 mac 的同事使用觸控板的方式都不太高效,還是使用普通鼠標那一套操作。不願更新自己的使用習慣。

針對以上原因,我要提問瞭:我們真的會運用鼠標和觸控板嗎?

這個問題包含兩層含義:

  1. 作為 mac 用戶:觸控板我們使用習慣是否高效。
  2. 作為前端開發者:觸控板相關的需求我們能否實現。

接下來我們帶著問題來尋找答案。

TL;DR

看好多國外的文章,太長瞭都會有 TL;DR(Too Long; Didn't Read.),我認為那不是 Too Long;Didn't Read,而是 Too Lazy; Didn't Read. 所以我也先把結論寫在這裡。

通過本文能學到的知識:

  1. 鼠標模式
    1. 鼠標滾輪縮放畫佈
    2. 右鍵長按拖拽平移畫佈
  2. 觸控板模式
    1. 雙指移動平移畫佈
    2. 雙指捏合縮放畫佈
    3. 禁用雙指輕掃在頁面間切換
    4. 禁用智能縮放
  1. 觸控板的高級用法

下面開始正文:

對 mac 操作很溜的選手可以忽略“觸控板使用”這節。但是我發現身邊大部分使用 mac 的同事都不怎麼會熟練使用觸控板的高級功能,還僅僅停留在能用的階段。我個人推薦你閱讀下升級自己的使用習慣。

—>>> 忽略開始 <<<—

1 基礎概念

1.1 鼠標、觸控板

如下圖所示分別是:Magic Trackpad 妙控板(文中統稱觸控板)、Magic Mouse 妙控鼠標、 普通鼠標。

Magic Trackpad 妙控板 / Magic Mouse 妙控鼠標 / 普通鼠標

本節所說的區別特指Apple 觸控設備和普通鼠標的交互區別。因為妙控板和妙控鼠標交互形式是一致的,都是單指、雙指點擊或者滑動以及多指滑動,而普通鼠標則是按鍵點擊與滾輪滾動與 Apple 觸控設備有著明顯的區別。

1.2 點擊和手勢

相信現在工作的大傢對鼠標的的操作習慣,都來源於 Windows 時代的人機交互。

而 Apple 設備改進瞭這一交互習慣。主要還是得益於硬件設備支持瞭多點觸控。有瞭多點觸控就可以增加更多的手勢交互。當年在鼠標時代就用過很多瀏覽器插件,支持鼠標手勢,通過鼠標畫一個預先設置好的圖形,就可以執行某些具體的操作,但是用鼠標畫圖還是有些難以操作。

我們直接來看 Apple 的手勢操作。

觸控板常用到的手勢操作有:

滾動縮放類:

  • 放大縮小
  • 智能縮放

這裡提出一個問題:明明是縮放,為什麼他們的設置項要叫滾動縮放?

更多手勢類:

  • 常規點擊操作
  • 在頁面之間輕掃
  • 在全屏幕 App 之間輕掃
  • 調度中心
  • App Exposé
  • 啟動臺
  • 顯示桌面

2 觸控板獨有功能以及推薦用法

這點是我要重點推薦的,觀察到大傢平時操作觸控板拖拽窗口,或者拖拽文件基本都是以下步驟:

  1. 觸控板移動光標到指定位置;
  2. 單指使勁按下觸控板;
  3. 開始拖動文件,此時還的一直使勁按著觸控板;
  4. 拖到指定位置松手。

到第 2、3 步的時候體驗太糟糕瞭。手指既要向下用力,又要向左右用力滑動,一不小心松勁瞭,拖著的文件又跑回原來的地方瞭,或者壓根拖到瞭不該去的地方。真是難受。

使用瞭下面推薦的方法之後,你就可以隨意拖動文件,而且毫不費力,

2.1 觸控板三指拖移

設置方法如下:

  • 設置 > 輔助功能 > 指針控制 > 鼠標與觸控板 > 觸控板選項

(觸控板三指拖移設置)

  • 如果懶得找那麼深的設置,也可以直接搜索:“拖移”。可以看到 Apple 給他的定位描述 “讓鼠標和觸控板更易於使用”

提醒:由於三指被占用,原來觸控板裡的“更多手勢”裡凡是用到三指的都要改成四指,避免沖突。接下來幾天你的操作可能會特別難受,但是習慣的升級總是要經歷一個過程的。

以上就是觸控板的最便於使用的方法。

—>>> 忽略結束 <<<—

接下來就是代碼部分瞭。

————————–我是分割線————————–

3 鼠標觸控板開發

文章開頭提出的第二個問題:我們要實現觸控板/鼠標的平移縮放功能該如何實現呢?相信大部分前端開發者第一反應想到監聽鼠標滾輪事件。沒錯,就是運用鼠標滾輪事件。

此時我又要提問瞭:

  • 觸控板的雙指捏合縮放頁面怎麼實現呢?
  • 觸控板的雙指任意方向滑動平移頁面該怎麼實現呢?
  • 觸控板又該如何禁用雙指輕掃在頁面間切換?

這些如果你還不太清楚,那就的接著往下看瞭。

下面我們繼續帶著問題來找答案。以下是我找答案的過程:

  1. 本著面向 Google 編程的思想,在最開始我先 Google 瞭一通 “觸控板 禁用 雙指 後退 返回 trackpad disable“ 等等關鍵詞,搜索結果都是移動端頁面觸控手勢,觸控板的文章少得可憐,基本都是介紹觸控板的使用,代碼相關的一無所獲。
  2. 後來我在想會不會觸控板有我不知道的雙指事件,我又去 developer.apple.com 一通搜索也是隻有 Mouse and Trackpad 的功能介紹,在深入就到 Appkit 瞭顯然這條路也不對。
  3. 一籌莫展之下 stack overflow 永遠不會讓你失望。在這裡找到瞭一個問題:Detect touchpad vs mouse in Javascript,心想反正也要判斷觸控板和鼠標,那就抄一下這個答案吧。抄答案時候發現基本用到的都是 mousewheel 事件。
  4. 所以 mdn 接著找 mousewheel 資料,發現 mousewheel: This feature is no longer recommended. 推薦用 wheel,似乎我們已經找到瞭解決問題的方法。

用到的對象主要是 WheelEvent。除他之外還有幾個關聯的事件對象要熟悉:MouseEventUIEventEvent。這些事件對象都是依次繼承下來的。

到這裡答案基本出來瞭解決上面的問題主要運用的就是:wheel 鼠標滾輪事件。到這裡也回答瞭上面的問題:為何雙指縮放等設置項會歸類到滾動縮放下,因為他們的實現都是使用瞭 wheel事件。

那我們就細細研究一下 wheel事件,先看他的幾個對我們來說有用的屬性:

  • WheelEvent.deltaX 隻讀:返回 double 值,該值表示滾輪的橫向滾動量。
  • WheelEvent.deltaY 隻讀:返回 double 值,該值表示滾輪的縱向滾動量。

從事件屬性可以看出該事件主要用來監聽鼠標滾輪的滾動,並可以拿到滾動量。為瞭搞的更清楚這幾個屬性所代表的意義,接下來我做瞭個小實驗,來探究 delta 的變化規律。

3.1 wheel 小實驗

實驗說明:頁面有兩個 div,內部 div 比外部的大,通過監聽滾輪事件滾動尋找 deltaX、deltaY 的規律。同時將 scrollTop、scrollLeft 打印出來進行參照看 div 的滾動方向。

<!-- 代碼直接新建個 html 全部 copy 進去即可看效果。 -->

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>你不知道的 Trackpad</title>
<style>
body {
background-color: #25252580;
overflow: hidden;
}
#root {
position: fixed;
top: calc(50% - 150px);
left: calc(50% - 150px);

width: 300px;
height: 300px;

/* 任意方向滾動 */
overflow: scroll;

/* 縱向滾動效果 */
/*overflow-x: hidden;*/
/*overflow-y: scroll;*/

/* 橫向滾動效果 */
/*overflow-x: scroll;*/
/*overflow-y: hidden;*/
}
h1 {
position: absolute;
top: 0;
left: 0;
}
#container {
width: 1000px;
height: 1000px;
/* 背景色漸變 */
background: linear-gradient(
217deg,
rgba(255, 0, 0, 0.8),
rgba(255, 0, 0, 0) 70.71%
), linear-gradient(
127deg,
rgba(0, 255, 0, 0.8),
rgba(0, 255, 0, 0) 70.71%
), linear-gradient(336deg, rgba(0, 0, 255, 0.8), rgba(0, 0, 255, 0) 70.71%);
}
</style>
</head>
<body>
灰色的都是body
<div id="root">
<h1>你不知道的 Trackpad</h1>
<div id="container"></div>
</div>

<script>
document.addEventListener(
"wheel",
function (e) {
e.stopPropagation();
e.preventDefault();
return false;
},
{ passive: false }
);
document.getElementById("root").addEventListener(
"wheel",
function (e) {
e.stopPropagation();
logInfo(e, "root");
isTrackpadOrMouse(e);
},
{ passive: false }
);

let hnum = 1;
let vnum = 1;
// 臨時存放日志,方便以表格展示方便對比結果
let temp = [];
// 是否一直打印日志
let always = false;

/**
* 重點看這幾個屬性:deltaX、deltaY、ctrlKey
*/
function logInfo(e, name) {
let info = {
name: name,
button: e.button,
ctrlKey: e.ctrlKey,
// altKey: e.altKey,
// metaKey: e.metaKey,
shiftKey: e.shiftKey,
// which: e.which,
// clientX: e.clientX,
// clientY: e.clientY,
deltaMode: e.deltaMode,
deltaX: e.deltaX,
deltaY: e.deltaY,
// deltaZ: e.deltaZ,
// layerX: e.layerX,
// layerY: e.layerY,
// offsetX: e.offsetX,
// offsetY: e.offsetY,
// wheelDelta: e.wheelDelta,
wheelDeltaX: e.wheelDeltaX,
wheelDeltaY: e.wheelDeltaY,
scrollTop: document.getElementById(name).scrollTop,
scrollLeft: document.getElementById(name).scrollLeft,
// x: e.x,
// y: e.y,
};
if (always || hnum % 10 === 0 || vnum % 10 === 0) {
temp.push(info);
}

// 垂直滾動時
if (e.deltaY > 0) {
vnum += 1;
} else if (e.deltaY < 0) {
vnum -= 1;
}

// 水平滾動時
if (e.deltaX > 0) {
hnum += 1;
} else if (e.deltaX < 0) {
hnum -= 1;
}

if (always || hnum === 0 || vnum === 0) {
console.table(always ? [info] : temp);
}
}

/**
* 判斷是鼠標或者觸控板
* @param e
* @return {boolean}
*/
function isTrackpadOrMouse(e) {
let isTrackpad = false;
if (e.wheelDeltaY) {
if (e.wheelDeltaY === e.deltaY * -3) {
isTrackpad = true;
}
} else if (e.deltaMode === 0) {
isTrackpad = true;
}

console.log(isTrackpad ? "Trackpad detected" : "Mousewheel detected");
return isTrackpad;
}
</script>
</body>
</html>