偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

圖形編輯器開(kāi)發(fā):快捷鍵的管理

開(kāi)發(fā) 前端
快捷鍵操作在圖形編輯器中是很高頻的操作,能讓用戶(hù)快速高效地執(zhí)行特定命令。那么今天就來(lái)學(xué)習(xí)圖形編輯器是如何做快捷鍵的管理的。

大家好,我是前端西瓜哥。

快捷鍵操作在圖形編輯器中是很高頻的操作,能讓用戶(hù)快速高效地執(zhí)行特定命令。

那么今天就來(lái)學(xué)習(xí)圖形編輯器是如何做快捷鍵的管理的。

圖片

編輯器 github 地址:

https://github.com/F-star/suika

線(xiàn)上體驗(yàn):

https://blog.fstars.wang/app/suika/

簡(jiǎn)單的快捷鍵綁定

我們先看看原生的鍵盤(pán)事件能否滿(mǎn)足需求。

假設(shè)我們需要判斷用戶(hù)是否按下了 Ctrl + C(需要精準(zhǔn)匹配),如果按下了就執(zhí)行 copy 方法。

用原生事件,我們要這樣寫(xiě):

window.addEventListener('keydown', (e) => {
  const { ctrlKey, shiftKey, altKey, metaKey } = e;
  if (ctrlKey && !shiftKey && !altKey && !metaKey && e.code === 'KeyC') {
    copy();
  }
})

寫(xiě)法有點(diǎn)繁瑣。我們希望能簡(jiǎn)化一下寫(xiě)法。

一開(kāi)始我并不太在意快捷鍵綁定的管理,因?yàn)閺?fù)雜度還沒(méi)起來(lái),就找了一個(gè)輪子 hotkeys-js。

import hotkeys from 'hotkeys-js';

hotkeys('ctrl+c', copy);

hotkeys-js 是原生事件的一層簡(jiǎn)單的封裝,簡(jiǎn)化了寫(xiě)法并提高了可讀性。

如果你的圖形編輯器并不復(fù)雜,用一些易用性不錯(cuò)的快捷鍵庫(kù)是不錯(cuò)的選擇。

快捷鍵高級(jí)能力

原生事件和一些常見(jiàn)的快捷鍵庫(kù)可以處理一些簡(jiǎn)單的場(chǎng)景,但圖形編輯器的場(chǎng)景往往更復(fù)雜。

圖形編輯器還需要的快捷鍵高級(jí)能力有:

  • 給一個(gè)行為設(shè)置多個(gè)不同快捷鍵,比如 Delete 或 Backspace 都可以刪除選中元素(這個(gè)大多第三方快捷鍵輪子是支持的);
  • 可以根據(jù)不同操作系統(tǒng)綁定不同的快捷鍵,比如復(fù)制,我希望在 Windows 系統(tǒng)為 Ctrl+C,在 MacOS 系統(tǒng)則是 Command+C。
  • 提供環(huán)境上下文,綁定的函數(shù)可以通過(guò)它決定是否被調(diào)用,比如我希望移動(dòng)圖形的時(shí)候不能執(zhí)行 Delete 對(duì)應(yīng)刪除操作。
  • 支持短路匹配,只執(zhí)行第一個(gè)匹配條件。這是為了防止快捷鍵沖突,一個(gè)快捷鍵執(zhí)行了多個(gè)行為。當(dāng)然如果你就是希望一個(gè)快捷鍵要執(zhí)行多個(gè)行為,那可以考慮補(bǔ)充一個(gè) next 方法。
  • 某個(gè)快捷鍵綁定可以設(shè)置為高優(yōu)先級(jí),比如激活某個(gè)工具時(shí),要注冊(cè)一些快捷鍵,需要高優(yōu)先級(jí),以便覆蓋掉和其他的同名快捷鍵。

快捷鍵管理類(lèi)

考慮上面這些功能點(diǎn),我們來(lái)實(shí)現(xiàn)這個(gè)快捷鍵管理類(lèi) KeyBindingManager。

class KeyBindingManager {
  // 傳入一個(gè)入口類(lèi)對(duì)象 Editor,之后需要用到它的變量
  constructor(private editor: Editor) {}
}

keyBinding 對(duì)象

一份快捷鍵綁定(keyBinding)由下面幾個(gè)部分組成:

key,快捷鍵描述。理論上應(yīng)該用 "Ctrl+C" 這種字符串來(lái)描述,但它實(shí)現(xiàn)起來(lái)比較麻煩,要解析,要轉(zhuǎn)換(比如 / 要轉(zhuǎn)成 Slash 去匹配 event.code)。

所以我換成了一個(gè)對(duì)象:{ CtrlKey: true, keyCode: 'KeyC' }。不用解析,不用轉(zhuǎn)換,直接和 event 的屬性對(duì)比即可。這個(gè)是 精準(zhǔn) 匹配,即不能有多余的修飾鍵。

此外,key 也支持傳入數(shù)組,這種情況比較少,對(duì)應(yīng)一個(gè)行為有多個(gè)快捷鍵的情況。比如刪除操作,我們可以傳入 [{ keyCode: 'Delete' }, { keyCode: 'Backspace' }]。

winKey,快捷鍵描述(Windows 特供版)。這個(gè)參數(shù)是可選的,如果不提供,所有系統(tǒng)都會(huì)使用 key 參數(shù)。如果提供,且用戶(hù)操作系統(tǒng)為 Windows,會(huì)使用  winKey,忽略 key。

when,是否滿(mǎn)足上下文。也是可選的。when 是一個(gè)方法,可以通過(guò)它拿到一些上下文參數(shù),通過(guò)這些參數(shù)決定返回的布爾值。如果為 true,表示匹配到了,并執(zhí)行對(duì)應(yīng)的響應(yīng)行為;如果為 false,沒(méi)匹配到,繼續(xù)找下一個(gè)。when 可不提供,表示永遠(yuǎn)滿(mǎn)足條件。

action,快捷鍵匹配后要執(zhí)行的方法。

TypeScript 類(lèi)型簽名為:

interface IKeyBinding {
  key: IKey | IKey[];
  winKey?: IKey | IKey[];
  when?: (ctx: IWhenCtx) => boolean;
  action: (e: KeyboardEvent) => void;
}

interface IKey {
  ctrlKey?: boolean;
  shiftKey?: boolean;
  altKey?: boolean;
  metaKey?: boolean;
  // KeyboardEvent['code'] 或 '*'(匹配任何按鍵)
  keyCode: string;
}

interface IWhenCtx {
  isToolDragging: boolean; // 是否在拖拽中(比如移動(dòng)工具移動(dòng)圖形中)
}

快捷鍵注冊(cè)

我們需要用有序表來(lái)根據(jù)注冊(cè)順序保存 keyBinding 的,這里我選擇用 Map 數(shù)據(jù)結(jié)構(gòu),它是一種有序數(shù)據(jù)結(jié)構(gòu)。

class KeyBindingManager {
  // 用 Map 
  private keyBindingMap = new Map<number, IKeyBinding>();
  private id = 0;
  
  //...
 
  // 注冊(cè)一個(gè)快捷鍵
  register(keybinding: IKeyBinding) {
    const id = this.id;
    this.keyBindingMap.set(id, keybinding);

    this.id++;
    return id;
  }
  
  // 注銷(xiāo)快捷鍵
  unregister(id: number) {
    this.keyBindingMap.delete(id);
  }
}

注冊(cè)方法 register 會(huì)返回一個(gè)唯一 id,如果需要注銷(xiāo),需要將這個(gè) id 傳給注銷(xiāo)方法 unregister。

事件的解綁方式有 3 種,這里選擇的是類(lèi)似 setTimeout 返回一個(gè)訂閱 id 的風(fēng)格。

事件訂閱的幾種實(shí)現(xiàn)風(fēng)格

實(shí)際上 3 種寫(xiě)法都沒(méi)啥差別,都是要把綁定事件方法返回的結(jié)果保存下來(lái),在合適的時(shí)機(jī)調(diào)用解綁方法。

哦對(duì)了,還有注冊(cè)高優(yōu)先級(jí)快捷鍵的方法:

class KeyBindingManager {
  // ...
  
  // 綁定一個(gè)高優(yōu)先級(jí)快捷鍵綁定(會(huì)放到 Map 的開(kāi)頭)
  registerWithHighPrior(keybinding: IKeyBinding) {
    const id = this.id;

    const map = new Map<number, IKeyBinding>();
    map.set(id, keybinding);

    for (const [key, val] of this.keyBindingMap) {
      map.set(key, val);
    }
    this.keyBindingMap = map;
    this.id++;
    return id;
  }
}

其實(shí)就是把這個(gè)快捷鍵注冊(cè)到 Map 的開(kāi)頭。

如果你需要更細(xì)的粒度,比如低優(yōu)先級(jí)、中優(yōu)先級(jí)、高優(yōu)先級(jí),那你可以考慮傳多一個(gè)優(yōu)先級(jí)枚舉值或一個(gè)數(shù)值,然后在正確的位置插入。感覺(jué)并沒(méi)有太多需要用到這種粒度的場(chǎng)景。

短路匹配邏輯

然后就是快捷鍵的匹配邏輯:

  • 匹配順序根據(jù)注冊(cè)順序(有特例,就是前面說(shuō)的高優(yōu)先級(jí)快捷鍵綁定,會(huì)插隊(duì),插到隊(duì)伍開(kāi)頭)。
  • 使用精準(zhǔn)匹配(key 或 winKey),以及 when 方法是否為 true,都為 true 時(shí)執(zhí)行 action。
  • 使用短路邏輯,即只執(zhí)行第一個(gè)匹配的(后面可能也有其他匹配的,但不執(zhí)行)。這個(gè)其實(shí)是設(shè)計(jì)模式的責(zé)任鏈模式,像是 express 或 koa 的路由匹配機(jī)制也是責(zé)任鏈模式。

實(shí)現(xiàn)如下:

const isWindows =
  navigator.platform.toLowerCase().includes('win') ||
  navigator.userAgent.includes('Windows');

class KeyBindingManager {
  
  // ...
  
  // 綁定到原生鍵盤(pán)按下事件上
  bindEvent() {
    if (this.isBound) return;
    this.isBound = true;
    document.addEventListener('keydown', this.handleAction);
  }
  
  // 找到匹配的 keyBinding,執(zhí)行其 action
  private handleAction = (e: KeyboardEvent) => {
    if (
      e.target instanceof HTMLInputElement ||
      e.target instanceof HTMLTextAreaElement
    ) {
      return;
    }

    let isMatch = false;
    
    // 生成上下文對(duì)象,可根據(jù)需要擴(kuò)充
    const ctx: IWhenCtx = {
      isToolDragging: this.editor.toolManager.isDragging,
    };

    for (const keyBinding of this.keyBindingMap.values()) {
      // 先看看 when 是否為 true(when 可不提供)
      if (!keyBinding.when || keyBinding.when(ctx)) {
        // 如果是 Windows 操作系統(tǒng),看看 winKey 對(duì)不對(duì)
        if (isWindows) {
          if (keyBinding.winKey && this.isKeyMatch(keyBinding.winKey, e)) {
            isMatch = true;
          }
        }
        // 其他操作系統(tǒng),看 key 是否匹配
        else if (this.isKeyMatch(keyBinding.key, e)) {
          isMatch = true;
        }
      }

      // 匹配
      if (isMatch) {
        e.preventDefault();
        keyBinding.action(e); // 執(zhí)行對(duì)應(yīng) action(行為)
        break; // 結(jié)束,不繼續(xù)遍歷
      }
    }
  };

  private isKeyMatch(key: IKey | IKey[], e: KeyboardEvent): boolean {
    if (Array.isArray(key)) {
      return key.some((k) => this.isKeyMatch(k, e));
    }

    if (key.keyCode == '*') return true;

    const {
      ctrlKey = false,
      shiftKey = false,
      altKey = false,
      metaKey = false,
    } = key;

    return (
      ctrlKey == e.ctrlKey &&
      shiftKey == e.shiftKey &&
      altKey == e.altKey &&
      metaKey == e.metaKey &&
      key.keyCode == e.code
    );
  }
}

用法舉例

類(lèi)寫(xiě)好了,看看用法。

刪除快捷鍵的寫(xiě)法:

const deleteAction = () => {
  // 刪除選中元素
};
editor.keybindingManager.register({
  // Backspace 或 Delete 都可以刪除
  key: [{ keyCode: 'Backspace' }, { keyCode: 'Delete' }],
  // 只能在沒(méi)有發(fā)生拖拽的情況下下刪除(比如移動(dòng)圖形時(shí)不能刪除)
  when: (ctx) => !ctx.isToolDragging,
  action: deleteAction,
});

復(fù)制快捷鍵的寫(xiě)法:

const copyHandler = () => {
  // 復(fù)制
}

editor.keybindingManager.register({
  key: { metaKey: true, keyCode: 'KeyC' },
  // Windows 環(huán)境下的快捷鍵
  winKey: { ctrlKey: true, keyCode: 'KeyC' },
  action: copyHandler,
});

一些優(yōu)化點(diǎn)

  • 如果你考慮一些非美式鍵盤(pán),比如法語(yǔ)鍵盤(pán),因?yàn)榘存I布局位置發(fā)生了變化,需要做鍵位的重映射,確保物理位置不變,確保用戶(hù)的肌肉記憶有效。
  • 簡(jiǎn)化快捷鍵描述的寫(xiě)法,使用類(lèi)似 Ctrl+/ 的更簡(jiǎn)潔寫(xiě)法。如果你需要類(lèi)似 VSCode 一樣提供 JSON 文件給支持用戶(hù)自己設(shè)置快捷鍵,這個(gè)還是要實(shí)現(xiàn)的。
責(zé)任編輯:姜華 來(lái)源: 前端西瓜哥
相關(guān)推薦

2024-01-03 15:28:37

Linuxnano編輯器

2023-10-19 10:12:34

圖形編輯器開(kāi)發(fā)縮放圖形

2023-09-26 07:39:21

2023-02-06 16:59:57

Canvas編輯器

2023-09-07 08:24:35

圖形編輯器開(kāi)發(fā)繪制圖形工具

2023-08-31 11:32:57

圖形編輯器contain

2024-01-08 08:30:05

光標(biāo)圖形編輯器開(kāi)發(fā)游標(biāo)

2023-09-11 09:02:31

圖形編輯器模塊間的通信

2023-04-07 08:02:30

圖形編輯器對(duì)齊功能

2023-08-28 08:10:50

Hex圖形編輯器

2023-10-10 16:04:30

圖形編輯器格式轉(zhuǎn)換

2023-01-18 08:30:40

圖形編輯器元素

2023-02-01 09:21:59

圖形編輯器標(biāo)尺

2009-06-16 13:53:00

netbeans 快捷

2010-07-08 13:39:36

LinuxUnix快捷鍵

2023-06-12 08:22:56

圖形編輯器工具

2009-06-09 16:41:46

NetBeans快捷鍵java

2024-01-03 08:43:17

圖形編輯器旋轉(zhuǎn)控制點(diǎn)縮放控制點(diǎn)

2023-07-07 13:56:01

圖形編輯器畫(huà)布縮放

2023-04-10 08:45:44

圖形編輯器排列移動(dòng)功能
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)