精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

圖形編輯器開發:快捷鍵的管理

開發 前端
快捷鍵操作在圖形編輯器中是很高頻的操作,能讓用戶快速高效地執行特定命令。那么今天就來學習圖形編輯器是如何做快捷鍵的管理的。

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

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

那么今天就來學習圖形編輯器是如何做快捷鍵的管理的。

圖片

編輯器 github 地址:

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

線上體驗:

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

簡單的快捷鍵綁定

我們先看看原生的鍵盤事件能否滿足需求。

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

用原生事件,我們要這樣寫:

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

寫法有點繁瑣。我們希望能簡化一下寫法。

一開始我并不太在意快捷鍵綁定的管理,因為復雜度還沒起來,就找了一個輪子 hotkeys-js。

import hotkeys from 'hotkeys-js';

hotkeys('ctrl+c', copy);

hotkeys-js 是原生事件的一層簡單的封裝,簡化了寫法并提高了可讀性。

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

快捷鍵高級能力

原生事件和一些常見的快捷鍵庫可以處理一些簡單的場景,但圖形編輯器的場景往往更復雜。

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

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

快捷鍵管理類

考慮上面這些功能點,我們來實現這個快捷鍵管理類 KeyBindingManager。

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

keyBinding 對象

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

key,快捷鍵描述。理論上應該用 "Ctrl+C" 這種字符串來描述,但它實現起來比較麻煩,要解析,要轉換(比如 / 要轉成 Slash 去匹配 event.code)。

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

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

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

when,是否滿足上下文。也是可選的。when 是一個方法,可以通過它拿到一些上下文參數,通過這些參數決定返回的布爾值。如果為 true,表示匹配到了,并執行對應的響應行為;如果為 false,沒匹配到,繼續找下一個。when 可不提供,表示永遠滿足條件。

action,快捷鍵匹配后要執行的方法。

TypeScript 類型簽名為:

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; // 是否在拖拽中(比如移動工具移動圖形中)
}

快捷鍵注冊

我們需要用有序表來根據注冊順序保存 keyBinding 的,這里我選擇用 Map 數據結構,它是一種有序數據結構。

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

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

注冊方法 register 會返回一個唯一 id,如果需要注銷,需要將這個 id 傳給注銷方法 unregister。

事件的解綁方式有 3 種,這里選擇的是類似 setTimeout 返回一個訂閱 id 的風格。

事件訂閱的幾種實現風格

實際上 3 種寫法都沒啥差別,都是要把綁定事件方法返回的結果保存下來,在合適的時機調用解綁方法。

哦對了,還有注冊高優先級快捷鍵的方法:

class KeyBindingManager {
  // ...
  
  // 綁定一個高優先級快捷鍵綁定(會放到 Map 的開頭)
  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;
  }
}

其實就是把這個快捷鍵注冊到 Map 的開頭。

如果你需要更細的粒度,比如低優先級、中優先級、高優先級,那你可以考慮傳多一個優先級枚舉值或一個數值,然后在正確的位置插入。感覺并沒有太多需要用到這種粒度的場景。

短路匹配邏輯

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

  • 匹配順序根據注冊順序(有特例,就是前面說的高優先級快捷鍵綁定,會插隊,插到隊伍開頭)。
  • 使用精準匹配(key 或 winKey),以及 when 方法是否為 true,都為 true 時執行 action。
  • 使用短路邏輯,即只執行第一個匹配的(后面可能也有其他匹配的,但不執行)。這個其實是設計模式的責任鏈模式,像是 express 或 koa 的路由匹配機制也是責任鏈模式。

實現如下:

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

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

    let isMatch = false;
    
    // 生成上下文對象,可根據需要擴充
    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 操作系統,看看 winKey 對不對
        if (isWindows) {
          if (keyBinding.winKey && this.isKeyMatch(keyBinding.winKey, e)) {
            isMatch = true;
          }
        }
        // 其他操作系統,看 key 是否匹配
        else if (this.isKeyMatch(keyBinding.key, e)) {
          isMatch = true;
        }
      }

      // 匹配
      if (isMatch) {
        e.preventDefault();
        keyBinding.action(e); // 執行對應 action(行為)
        break; // 結束,不繼續遍歷
      }
    }
  };

  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
    );
  }
}

用法舉例

類寫好了,看看用法。

刪除快捷鍵的寫法:

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

復制快捷鍵的寫法:

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

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

一些優化點

  • 如果你考慮一些非美式鍵盤,比如法語鍵盤,因為按鍵布局位置發生了變化,需要做鍵位的重映射,確保物理位置不變,確保用戶的肌肉記憶有效。
  • 簡化快捷鍵描述的寫法,使用類似 Ctrl+/ 的更簡潔寫法。如果你需要類似 VSCode 一樣提供 JSON 文件給支持用戶自己設置快捷鍵,這個還是要實現的。
責任編輯:姜華 來源: 前端西瓜哥
相關推薦

2024-01-03 15:28:37

Linuxnano編輯器

2023-10-19 10:12:34

圖形編輯器開發縮放圖形

2023-09-26 07:39:21

2023-02-06 16:59:57

Canvas編輯器

2023-09-07 08:24:35

圖形編輯器開發繪制圖形工具

2023-08-31 11:32:57

圖形編輯器contain

2024-01-08 08:30:05

光標圖形編輯器開發游標

2023-09-11 09:02:31

圖形編輯器模塊間的通信

2023-04-07 08:02:30

圖形編輯器對齊功能

2023-01-18 08:30:40

圖形編輯器元素

2023-02-01 09:21:59

圖形編輯器標尺

2023-10-10 16:04:30

圖形編輯器格式轉換

2023-08-28 08:10:50

Hex圖形編輯器

2009-06-16 13:53:00

netbeans 快捷

2023-06-12 08:22:56

圖形編輯器工具

2010-07-08 13:39:36

LinuxUnix快捷鍵

2009-06-09 16:41:46

NetBeans快捷鍵java

2023-07-07 13:56:01

圖形編輯器畫布縮放

2024-01-03 08:43:17

圖形編輯器旋轉控制點縮放控制點

2023-04-10 08:45:44

圖形編輯器排列移動功能
點贊
收藏

51CTO技術棧公眾號

久久黄色网页| 欧美freesex8一10精品| 亚洲色图视频免费播放| 91亚洲精品丁香在线观看| 日韩精品国产一区二区| 亚洲综合小说图片| 91麻豆精品国产91久久久使用方法 | 懂色av蜜臀av粉嫩av分享吧最新章节| 精品日韩一区| 精品久久久久久久久久久久包黑料 | 黄av在线播放| 91视频国产资源| 91沈先生在线观看| wwwwww国产| 欧美一区成人| 亚洲四色影视在线观看| 色欲欲www成人网站| a欧美人片人妖| 一区二区欧美在线观看| 日本一区二区在线| 免费的黄色av| 精品无码三级在线观看视频| 97热在线精品视频在线观看| 国产麻豆视频在线观看| 久久91成人| 欧美成人a视频| 午夜宅男在线视频| 中国色在线日|韩| 一区av在线播放| 一区二区三区欧美成人| 天堂а√在线8种子蜜桃视频| 国产在线不卡一卡二卡三卡四卡| 国产91免费看片| 国产精品白浆一区二小说| 99热在线成人| 中文字幕无线精品亚洲乱码一区 | 亚洲少妇30p| 图片区小说区区亚洲五月| 日韩一卡二卡在线| 高潮精品一区videoshd| 亚洲精品欧美日韩专区| 亚洲一区中文字幕在线| 视频在线在亚洲| 9.1国产丝袜在线观看| 国产一级淫片免费| 欧美一区视频| 欧美大胆在线视频| 久久人妻无码aⅴ毛片a片app| 国产精品一区二区三区av麻 | 91亚洲精品久久久| 艳妇乳肉豪妇荡乳av| 免费在线看一区| 国产国语刺激对白av不卡| 91视频免费网址| 亚洲每日更新| 2024亚洲男人天堂| 天堂а√在线中文在线新版| 国产精品综合| 欧美最猛性xxxxx亚洲精品| 日本天堂在线视频| 亚洲一区国产一区| 日本一区二区三区在线播放| 免费黄色片视频| 日韩影院在线观看| 国产精品女视频| 亚洲最大成人av| 国产美女精品一区二区三区| 99精彩视频在线观看免费| 99riav国产| 成人av在线一区二区三区| 国产一区二区三区av在线| 亚洲人妻一区二区| 久久精品人人做人人爽人人| 日本视频一区二区在线观看| eeuss影院www在线播放| 中文字幕永久在线不卡| 在线观看av的网址| av影院在线免费观看| 色噜噜久久综合| 久热精品在线观看视频| 99精品在线免费观看| 欧美va在线播放| 国产精品无码一区二区三| 国产99亚洲| 久久久精品亚洲| 国产精品99精品| 日韩电影一二三区| yy111111少妇影院日韩夜片| 外国精品视频在线观看 | 精品噜噜噜噜久久久久久久久试看 | 特级西西人体www高清大胆| 欧美人体视频xxxxx| 精品久久久久久国产| 波多结衣在线观看| 91综合久久爱com| 亚洲人成电影在线观看天堂色| 成人欧美一区二区三区黑人一 | 茄子视频成人免费观看| 国产三级一区| 亚洲电影免费观看| 天天摸日日摸狠狠添| 伊人青青综合网| 欧美亚洲伦理www| 亚洲天堂手机版| av高清久久久| 在线观看日韩羞羞视频| 国产资源在线观看入口av| 欧美日韩电影一区| 国产黑丝在线观看| 五月天久久777| 欧美在线观看网址综合| 99久久精品日本一区二区免费| 久久午夜羞羞影院免费观看| 欧美美女黄色网| 78精品国产综合久久香蕉| 欧美大片在线观看| 长河落日免费高清观看| 性一交一乱一区二区洋洋av| 亚洲自拍中文字幕| av资源种子在线观看| 亚洲国产va精品久久久不卡综合| 自拍偷拍21p| 天天操综合520| 欧美激情视频在线观看| 国产精品成人久久久| 99久久婷婷国产精品综合| 午夜啪啪福利视频| 高清av一区| 亚洲美女视频网站| 日韩高清免费av| 国产成人自拍在线| 五月天男人天堂| 精品美女一区| 亚洲视频网站在线观看| 羞羞影院体验区| 99精品视频一区二区| 亚洲乱码日产精品bd在线观看| 欧美高清xxx| 在线精品国产成人综合| 精品国产午夜福利| 97aⅴ精品视频一二三区| 男人天堂网站在线| 激情不卡一区二区三区视频在线| 波霸ol色综合久久| 在线黄色av网站| 国产精品久久久久久亚洲毛片 | 日韩免费小视频| 亚洲美女在线视频| 天天综合网久久综合网| 91丨porny丨首页| 国产老熟妇精品观看| 国产精品qvod| 97在线视频免费播放| 三级网站在线看| 欧美日韩国产中文字幕 | 亚洲国产成人自拍| 日韩av在线中文| 91欧美在线| 成人免费在线网址| av在线看片| 精品国产一区二区在线观看| 久久久久久久久久久网| 成人免费视频视频在线观看免费| 久久这里只有精品18| 国产精品中文字幕制服诱惑| 午夜精品久久久久久99热| 特黄视频在线观看| 在线日韩国产精品| 精品国产大片大片大片| 国产精品一区三区| 欧美精品久久久久久久久久久| 日韩精品免费一区二区夜夜嗨| 欧美一级片免费在线| 久蕉依人在线视频| 欧美电影影音先锋| 精品在线免费观看视频| 91视频一区二区| 欧美特级aaa| 欧美日本国产| 蜜桃臀一区二区三区| 精品成人免费一区二区在线播放| 深夜福利亚洲导航| 亚洲国产精品二区| 欧美天堂在线观看| 男女男精品视频网站| 国产成人av影院| www.四虎成人| 99久久www免费| 国产精品视频入口| 国产一区二区主播在线| 欧美成人免费全部| 日本啊v在线| 8v天堂国产在线一区二区| 精品处破女学生| 国产色一区二区| 麻豆免费在线观看视频| 久久婷婷一区| 波多野结衣与黑人| 欧美综合在线视频观看 | 亚洲成人一级片| 日本高清视频一区二区| 国产这里有精品| 国产日韩精品一区二区浪潮av| 樱花草www在线| 免费在线亚洲欧美| 神马午夜伦理影院| 欧美精品第一区| 超碰97在线资源| jizz久久久久久| 88xx成人精品| 在线观看免费视频你懂的| 亚洲视频免费一区| 人妻无码中文字幕| 欧美久久久久久久久久| 4438国产精品一区二区| 自拍偷自拍亚洲精品播放| 久久久久久九九九九九| 成人午夜大片免费观看| 日本人69视频| 久久先锋影音| 精品久久久久久久久久中文字幕| 亚洲精品国产首次亮相| 日韩精品一线二线三线| 美女一区2区| 99在线观看视频| 国产精品日本一区二区三区在线| 日韩av男人的天堂| 中文在线а√在线8| 欧美激情亚洲精品| 51xtv成人影院| 日韩在线免费视频观看| 日韩av视屏| 亚洲精品xxxx| 欧美熟妇交换久久久久久分类| 欧美高清激情brazzers| 中文字幕+乱码+中文乱码www| 日韩欧美国产黄色| 在线观看免费国产视频| 亚洲午夜视频在线| 激情小说中文字幕| 中文一区在线播放| 日本人亚洲人jjzzjjz| 日韩成人精品| 国产精品免费无码| 99视频一区| 欧美 亚洲 视频| 欧美一区不卡| 女人床在线观看| 欧美激情麻豆| 青青草综合在线| 国产精品久久| 国产真实老熟女无套内射| 欧美日韩亚洲一区| 欧美黄网在线观看| 在线不卡视频| 无码专区aaaaaa免费视频| 最新亚洲视频| 欧美在线观看成人| 国产婷婷精品| 久久精品99国产| 日本欧美一区二区三区| 狠狠躁狠狠躁视频专区| 精彩视频一区二区| 深夜视频在线观看| 成人少妇影院yyyy| 久久偷拍免费视频| 国产嫩草影院久久久久| 国产jizz18女人高潮| 亚洲色图在线播放| 久久亚洲精品大全| 欧美日韩亚洲成人| 懂色av中文字幕| 欧美肥妇毛茸茸| 黄色一级a毛片| 亚洲色图综合久久| 国产1区在线| 国模视频一区二区| 高清不卡av| 国产精品影片在线观看| 亚洲国产中文在线| 鲁丝一区二区三区免费| 日韩理论电影| 人妻激情另类乱人伦人妻| 国产亚洲午夜| 久久人人爽av| 成人一道本在线| 亚洲精品色午夜无码专区日韩| 国产精品萝li| 久久网免费视频| 日本韩国欧美在线| 性一交一乱一乱一视频| 亚洲品质视频自拍网| 国产最新在线| 欧美一二三视频| www.久久久久爱免| 国产一区免费观看| 欧美顶级大胆免费视频| 给我免费播放片在线观看| 美女诱惑一区二区| 成人区人妻精品一区二| 国产精品免费网站在线观看| 国产小视频在线看| 欧美四级电影网| 欧洲av在线播放| 久久精品福利视频| 在线高清av| 国产成人精品自拍| 999国产精品| 国产女女做受ⅹxx高潮| 粉嫩一区二区三区在线看| ass极品国模人体欣赏| 午夜伊人狠狠久久| 国产男女猛烈无遮挡| 亚洲图片在区色| caoporn视频在线观看| 91在线视频九色| 成人在线免费观看网站| 男人的天堂狠狠干| 国产一区二区美女诱惑| 成人在线手机视频| 欧美日韩亚洲激情| 高清毛片aaaaaaaaa片| 久久综合伊人77777| 日本欧美一区| 日本精品国语自产拍在线观看| 亚洲国产三级| 欧美午夜精品一区二区| 亚洲理论在线观看| 国产精品久久久久精| 在线视频免费一区二区| 女生影院久久| 久久久com| 99在线精品免费视频九九视| 色婷婷狠狠18禁久久| 亚洲欧洲日韩在线| 中文区中文字幕免费看| 亚洲视屏在线播放| 唐人社导航福利精品| 久久人人爽爽人人爽人人片av| 影音先锋久久精品| 欧产日产国产精品98| 亚洲制服丝袜一区| 亚洲精品97久久中文字幕无码| 欧美成人四级hd版| 精品久久久久久久久久岛国gif| 亚洲一区三区电影在线观看| 青青草伊人久久| 人妻无码一区二区三区免费| 欧美亚洲国产bt| 午夜在线免费观看视频| 国产精品中文字幕久久久| 99久久www免费| av在线免费观看不卡| 一区二区三区日本| 亚洲av综合色区无码一区爱av| 欧美成人第一页| 成人福利一区| 日本免费黄视频| 久久久午夜精品| 中国一级片黄色一级片黄| 一区国产精品视频| 欧美成人高清视频在线观看| 香蕉视频在线网址| 国产99精品国产| 免费看日韩毛片| 亚洲午夜未满十八勿入免费观看全集| 户外露出一区二区三区| 亚洲国产欧美不卡在线观看| 国精产品一区一区三区mba视频| 午夜激情福利网| 亚洲国产毛片完整版| 欧美日韩免费看片| 亚洲乱码国产乱码精品天美传媒| 久久精品国产99国产| 国产一级大片在线观看| 亚洲欧洲视频在线| 四虎国产精品永久在线国在线| 免费cad大片在线观看| 久久这里只精品最新地址| 精品国产www| 久久久久久久国产精品视频| 自拍亚洲一区| 亚洲日本黄色片| 五月天亚洲婷婷| 在线免费观看黄| 国产精品一区二区你懂得| 久久久久网站| 欧产日产国产v| 亚洲女人天堂成人av在线| 婷婷精品久久久久久久久久不卡| 成人午夜免费在线| 国产精品久久久久久户外露出 | 亚洲黄色有码视频| 日韩一级二级| 性欧美大战久久久久久久| 国产欧美日韩一区二区三区在线观看| 国产女人高潮的av毛片| 18久久久久久| 亚洲欧美综合久久久| 蜜桃av免费看|