自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖形編輯器開(kāi)發(fā):自定義光標(biāo)

開(kāi)發(fā) 前端
光標(biāo)(游標(biāo))在圖形界面交互中是非常基礎(chǔ)的一環(huán)。它是一個(gè)指針,懸浮在屏幕的最上層。除了可以標(biāo)記出指針的當(dāng)前位置,同時(shí)也會(huì)通過(guò)它獨(dú)特的樣式,提示用戶(hù)此時(shí)可以執(zhí)行怎么的操作。

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

今天來(lái)講講如何在圖形編輯器中使用自定義光標(biāo),并對(duì)光標(biāo)其進(jìn)行管理。

編輯器 github 地址:

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

線上體驗(yàn):

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

自定義光標(biāo)的意義是什么?

光標(biāo)(游標(biāo))在圖形界面交互中是非?;A(chǔ)的一環(huán)。

它是一個(gè)指針,懸浮在屏幕的最上層。除了可以標(biāo)記出指針的當(dāng)前位置,同時(shí)也會(huì)通過(guò)它獨(dú)特的樣式,提示用戶(hù)此時(shí)可以執(zhí)行怎么的操作。

比如抓手(grab)光標(biāo),是一個(gè)展開(kāi)的手掌,表示可以對(duì)目標(biāo)進(jìn)行拖拽操作。

縮放(xx-resize)光標(biāo),是一個(gè)有方向的單(雙)箭頭,表示可以往特定方向移動(dòng)以改變目標(biāo)大小。

長(zhǎng)得像英文字母 I 的文字(text)光標(biāo),則提示可以進(jìn)行文字的操作,細(xì)瘦的垂直線是為了更好地點(diǎn)中字符之間的空白區(qū)域。

點(diǎn)擊(pointer)光標(biāo),一根手指(食指,不是中指)伸出來(lái)是要干嘛,是為了試探,看到按鈕就嘗試點(diǎn)一下,表示某個(gè)區(qū)域是可點(diǎn)擊的。

操作系統(tǒng)有豐富的光標(biāo)樣式可以選擇,在 Web 網(wǎng)頁(yè)中可以通過(guò)  cursor 樣式屬性進(jìn)行設(shè)置。

對(duì)于一般應(yīng)用來(lái)說(shuō),通常是夠用的。但對(duì)于一個(gè)成熟的圖形編輯器來(lái)說(shuō),這還遠(yuǎn)遠(yuǎn)不夠。

我們還需要一些 更具體的光標(biāo)樣式來(lái)向用戶(hù)傳遞信息,比如:

  • 旋轉(zhuǎn)光標(biāo):表示圖形可旋轉(zhuǎn)。cursor 屬性中沒(méi)有旋轉(zhuǎn)光標(biāo),勉強(qiáng)可用抓手工具做個(gè)平替;
  • 支持任意度數(shù)的縮放光標(biāo)。cursor 屬性的縮放光標(biāo)只有 45 度的正數(shù)倍數(shù)光標(biāo),這精度遠(yuǎn)遠(yuǎn)不夠。
  • 鋼筆工具相關(guān)光標(biāo):鋼筆光標(biāo)、錨點(diǎn)光標(biāo)、新增/刪除點(diǎn)光標(biāo);

等等。

此外,自定義光標(biāo)還有一個(gè)很重要的作用,就是 實(shí)現(xiàn)不同平臺(tái)的視覺(jué)一致性。

不同操作系統(tǒng)的 UI 風(fēng)格是不同的,它們的光標(biāo)是相當(dāng)不一致的,會(huì)給用戶(hù)帶來(lái)不同的體驗(yàn)。

(我希望在 Windows 系統(tǒng)看到 MacOS 的光標(biāo))

如何支持自定義光標(biāo)

沒(méi)有光標(biāo),我們自己造。

好在 cursor 是支持自定義光標(biāo)的。

具體用法如下。

.suika-cursor-default {
  cursor: url(./cursor-icons/suika-cursor-default.png) 5 5, pointer;
}

值依次為:

  • url(<url>):自定義光標(biāo)的圖片資源 url,因?yàn)椴淮笄也幌M~外作為單獨(dú)資源加載,通常會(huì)選擇轉(zhuǎn)換為 base64 格式內(nèi)嵌;
  • x y:使用相對(duì)圖片左上角的像素位置作為光標(biāo)位置;
  • <keyword>:如果沒(méi)有指定自定義光標(biāo)圖片,或者加載光標(biāo)資源失敗,就會(huì)使用瀏覽器支持的光標(biāo)值,比如  pointer。

我們需要繪制好光標(biāo)圖片,然后導(dǎo)出為 png(背景為透明度),然后定義好 x 和 y,再通過(guò) css 類(lèi)包裹一下,然后根據(jù)需要在 Canvas 上設(shè)置對(duì)應(yīng)的 css 樣式即可。

多種旋轉(zhuǎn)角度的旋轉(zhuǎn)和縮放光標(biāo)

有兩種光標(biāo)比較特殊,它們有特殊的旋轉(zhuǎn)角度的參數(shù)。

它們就是旋轉(zhuǎn)和縮放光標(biāo)。

因?yàn)?cursor 這個(gè) css 屬性并不支持設(shè)置旋轉(zhuǎn)角度,所以我們只能繪制 0 到 359 之間度數(shù)共 360 個(gè)不同的旋轉(zhuǎn)光標(biāo)圖片。

縮放光標(biāo)因?yàn)槠錁邮街行膶?duì)稱(chēng)的原因,倒是不需要這么多,只要繪制 0 到 179 共 180 個(gè)圖片。

然后是 精細(xì)度的問(wèn)題。

你這里可以整一些貓膩,比如偷懶,抽走一些度數(shù),只給偶數(shù)的度數(shù),比如 2、4,奇數(shù)的度數(shù)都丟掉,沒(méi)有 1、3 這些度數(shù)。設(shè)置光標(biāo)的時(shí)候舍入一下,找最接近的度數(shù)。

或者你精益求精,你說(shuō)間隔 1 度未免太大,我們要更精確一點(diǎn),我們不僅支持整數(shù),我們還要支持 1.5、6.5 這種中間值,我們要用 720 個(gè)圖片。

沒(méi)問(wèn)題,都可以上。

批量生成方案

但是呢,我們發(fā)現(xiàn),這些光標(biāo)其實(shí)都來(lái)自一個(gè)源圖片,只是旋轉(zhuǎn)了不同的角度,我們手工一個(gè)個(gè)操作未免太低效了。

這時(shí)候我們就可以自己寫(xiě)或找一些工具,批量對(duì)一張?jiān)磮D形生成旋轉(zhuǎn)多種角度后的圖片,然后再寫(xiě)個(gè)腳本去自動(dòng)生成 css 代碼,把這些圖片引入進(jìn)去。

這是一個(gè)方案,figma 是這么做的。

感覺(jué)還是有億點(diǎn)麻煩。沒(méi)事,我們有另一個(gè)方案。

上面做的是打包前生成大量圖片,那我們可不可以在運(yùn)行時(shí)動(dòng)態(tài)生成光標(biāo)呢?

可以的。圖片有位圖的,也有矢量的啊,我們可以用一種叫做 SVG 的特殊圖片格式,它的內(nèi)容是文本,一種的 xml 文本。

我們可以將光標(biāo) UI 導(dǎo)出為 SVG,然后在最頂層的元素加上 transform 的旋轉(zhuǎn)變換。

可以寫(xiě)一個(gè)方法,傳入角度和位置信息,動(dòng)態(tài)生成對(duì)應(yīng)的 SVG 字符串,然后轉(zhuǎn)成 DataURL 給 cursor 應(yīng)用上。

大概像這樣:

const getRotationIconDataUrl = (degree, x = 0, y = 0) => {
  return `url("data:image/svg+xml,
   ...
   <g fill='none' transform='rotate(${degree} ${x} ${y})'
   ...
  ") ${x} ${y}, pointer`
}

canvas.style.cursor = getRotationIconDataUrl(114.544);

開(kāi)源白板工具 tldraw 選擇了這個(gè)方案。

你可以給一個(gè)精度很高的旋轉(zhuǎn)度數(shù)。

模塊設(shè)計(jì)

代碼設(shè)計(jì)上,我們會(huì)設(shè)計(jì)一個(gè) CursorManager 類(lèi)進(jìn)行光標(biāo)的管理。

這個(gè)類(lèi)最重要的作用就是設(shè)置光標(biāo)值。

setCursor 方法接收一個(gè)光標(biāo)值,除了支持傳統(tǒng)的字符串,也支持 { type: 'rotation'; degree: 45 } 這種形式。

它主要做了如下操作:

  • 標(biāo)準(zhǔn)化光標(biāo)值,比如把度數(shù)取余到 0~360 內(nèi)。
  • 比較新舊光標(biāo)值,相同就跳過(guò)。
  • 清空原來(lái)設(shè)置的光標(biāo)樣式。
  • 根據(jù)光標(biāo)不同,執(zhí)行各自的邏輯。

下面是核心代碼實(shí)現(xiàn)。

// 支持的光標(biāo)類(lèi)型
export type ICursor =
  | 'default'
  | { type: 'resize'; degree: number }
  | { type: 'rotation'; degree: number }
  | 'grab'
 | ...
}

class CursorManager {
  private cursor: ICursor;

  setCursor(cursor: ICursor) {
    // 1. 標(biāo)準(zhǔn)化光標(biāo)值,比如把度數(shù)取余到 0~360 內(nèi)
    cursor = this.normalizeCursor(cursor);
    // 2. 比較新舊光標(biāo)值,相同就跳過(guò)
    if (isEqual(cursor, this.cursor)) {
      return;
    }
    this.cursor = cursor;
    
    const clsPrefix = 'suika-cursor-';
    // 3. 清空原來(lái)設(shè)置的光標(biāo)樣式
    canvasElement.classList.forEach((className) => {
      if (className.startsWith(clsPrefix) {
        canvasElement.classList.remove(className);
      }
    });
    this.editor.canvasElement.style.cursor = '';
    
    // 4. 根據(jù)光標(biāo)不同,執(zhí)行各自的邏輯
    if (this.customClassCursor.has(cursor)) {
      // 這個(gè)是注冊(cè)了 class 的光標(biāo)
      const className = `${clsPrefix}${cursor}`;
      this.editor.canvasElement.classList.add(className);
    } else if (typeof cursor == 'string') {
      // 用瀏覽器自帶的
      this.editor.canvasElement.style.cursor = cursor;
    } else if (cursor.type === 'resize') {
      // 后面都是使用動(dòng)態(tài) svg 字符串
      this.setResizeCursorInCanvas(cursor.degree);
    } else if (cursor.type === 'rotation') {
      this.setRotationCursorInCanvas(cursor.degree);
    }
  }
}

繪制在畫(huà)布上的光標(biāo)

光標(biāo)還有一種比較少用的方案,也說(shuō)說(shuō)吧。

就是有些光標(biāo)是繪制在畫(huà)布上的。

一個(gè)經(jīng)典的例子就是 AutoCAD 的十字光標(biāo),這個(gè)十字的長(zhǎng)度是可以設(shè)置的,可以相當(dāng)長(zhǎng)。

如果你修改操作系統(tǒng)的光標(biāo),那這個(gè)十字便會(huì)突破天際地顯示到非繪制區(qū)域上。

此外,AutoCAD 的光標(biāo)并不忠實(shí)跟隨操作系統(tǒng)光標(biāo),比如有時(shí)候會(huì)吸附于某點(diǎn)不動(dòng),并基于它的位置顯示下拉菜單,此時(shí)可以用真正的光標(biāo)去點(diǎn)選。

考慮到性能,建議把光標(biāo)放到另一個(gè) canvas 上,和圖形放一個(gè) canvas 會(huì)讓畫(huà)布中沒(méi)做任何操作的圖形頻繁重繪。

結(jié)尾

總結(jié)一下。

關(guān)于圖形編輯器的光標(biāo),我們有以下方案:

  • 使用瀏覽器本身就提供的一些光標(biāo)值。優(yōu)點(diǎn)是成本低,缺點(diǎn)是樣式有限,且不同操作系統(tǒng)風(fēng)格差異大;
  • cursor 支持自定義光標(biāo),所以我們可以自己設(shè)置自己的一套光標(biāo)去應(yīng)用。但其中有一些比較特殊的有各種旋轉(zhuǎn)方向的光標(biāo),需要做特別的處理。一種是用工具批量生產(chǎn)光標(biāo)圖片,一種是利用 svg 在運(yùn)行時(shí)動(dòng)態(tài)生成;
  • 最后是在畫(huà)布上渲染光標(biāo)的方案,適合一些有特殊需求的圖形編輯器。這類(lèi)圖形編輯器的光標(biāo)往往可以自定義,且可以非常大,或是它們?cè)谀承﹫?chǎng)景下會(huì)脫離鼠標(biāo)的控制,喜歡特立獨(dú)行,比如突然吸附到某個(gè)吸附點(diǎn)上。缺點(diǎn)是實(shí)現(xiàn)比較復(fù)雜,你可能需要像管理圖形一樣去管理它。
責(zé)任編輯:姜華 來(lái)源: 前端西瓜哥
相關(guān)推薦

2023-10-20 08:02:25

圖形編輯器前端

2023-07-07 13:56:01

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

2011-03-17 09:45:01

Spring

2023-10-19 10:12:34

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

2010-11-16 13:21:08

Oracle命令行

2023-08-31 11:32:57

圖形編輯器contain

2023-09-07 08:24:35

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

2023-09-26 07:39:21

2023-09-11 09:02:31

圖形編輯器模塊間的通信

2023-08-28 08:10:50

Hex圖形編輯器

2023-10-08 08:11:40

圖形編輯器快捷鍵操作

2023-10-10 16:04:30

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

2024-01-03 08:43:17

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

2023-01-18 08:30:40

圖形編輯器元素

2023-02-01 09:21:59

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

2023-07-31 08:46:07

圖形編輯器圖形自動(dòng)對(duì)齊

2023-02-06 16:59:57

Canvas編輯器

2023-04-07 08:02:30

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

2023-06-12 08:22:56

圖形編輯器工具

2023-05-09 08:15:32

圖形編輯器撤銷(xiāo)重做功能
點(diǎn)贊
收藏

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