成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

圖形編輯器開發:實現縮放圖形

開發 前端
旋轉度數通常要配合一個變換中心(Origin),這個可以作為一個屬性讓用戶設置。但我更建議將 x、y、Width、Height 形成的 矩形的中點 作為旋轉中心,這樣更簡單一些,減少用戶的心智負擔,也防止出現用戶設置一些奇怪 Origin 的場景。

編輯器 github 地址:

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

線上體驗:

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

圖形的屬性

圖形有幾個重要的基礎屬性,會經常被用到,我們在實現縮放圖形前需要理清一下它們。

  • x / y
  • width / height
  • rotation

位置和大小

x 和 y 為圖形的左上角位置,注意是旋轉前的。

x、y 旋轉后我們叫做 rotatedX、rotatedY,屬性面板中會用到。

width 和 height 為圖形的寬高,這個沒什么好說的。

另外,有些圖形有些特殊,它的 x、y、width、height 是要通過其他屬性計算出來的,比如貝塞爾曲線。

旋轉

rotation 為圖形的旋轉度數,通常使用 弧度單位。

因為弧度是數學計算中的???,各種 API 都是要求提供弧度的,比如內置的 Math.sin() 方法。

你存角度自然也是可以,但不推薦,但計算時多了一層多余的單位轉換,且丟失一些微小的精度。

當然 UI 層還是要展示角度,因為是面向用戶的,對于數據和 UI 不統一的問題,在 UI 層做一個轉換即可。

旋轉度數通常要配合一個變換中心(origin),這個可以作為一個屬性讓用戶設置。

但我更建議將 x、y、width、height 形成的 矩形的中點 作為旋轉中心,這樣更簡單一些,減少用戶的心智負擔,也防止出現用戶設置一些奇怪 origin 的場景。

下圖中,紅色矩形是藍色矩陣順時針旋轉 45 度得到。

旋轉度數還要考慮 旋轉方向、基準角度、取值范圍 問題。

(因為弧度不直觀,后面會用角度來描述,但數據層依舊還是用的弧度)

  • 旋轉方向:設置旋轉后,圖形是會往順時針方向還是逆時針方向旋轉。
  • 基準角度:朝向哪里是 0 度。
  • 取值范圍:通常為 [0, 360) 和 (-180, 180]。二者其實等價,只是顯示有區別,后者其實只是前者減去 180 度。

通常這些編輯器自己決定就好。像我的項目,向上表示 0 度,順時針方向為旋轉方向,方向取值為 [0, 360)。

一些編輯器是支持用戶自己設置的,比如 AutoCAD 可通過圖形單位命令,設置旋轉方向和基準角度。

圖片

縮放實現思路

進入正題,對圖形進行縮放。

接下來會以通過右下角(也叫東南 se 方向) 縮放控制點縮放為例進行講解。

交互邏輯:

選擇工具下,當光標落在右下角的縮放控制點上時,光標會變成縮放樣式(這個不是本文核心,不講)。

此時按下鼠標,然后進行拖拽,即可對圖形以左上角為縮放中心,進行縮放。

實現思路:更新 width 和 height,然后確定參照點,修正 x  和 y。

按下鼠標時,我們要把當前圖形的 x、y、width、height、rotation 記錄下來。之后的縮放是基于這個初始狀態進行的。

const mousedown = (e) => {
  // ...
  
  // 縮放前圖形的屬性,之后我們會直接更新圖形屬性,導致原來的屬性丟失,所以要記錄下這個快照。
  prevElement = {
    x: item.x,
    y: item.y,
    width: item.width,
    height: item.height,
    rotation: item.rotation ?? 0,
  }
}

拖拽時,調用我們將要實現的 movePoint 方法,去更新這個圖形。

const drag = (e) = {
  // ...
  
  selectElement.movePoint(
    'se', // 縮放控制點類型:右下(或東南)
    lastPoint, // 當前光標位置(基于場景坐標系)
    prevElement, // 縮放前的屬性快照
  );
}

下面就是核心方法 movePoint 的實現邏輯了。

更新 width 和 height

首先是更新矩形寬高。

因為有一個旋轉,所以算法不會這么直觀。

我們要意識到這里有一個變換。看到的圖形,是做過變換(基于矩形中心旋轉)之后的,但我們需要修改的 width、height、x、y 則是旋轉前的。

所以我們需要把光標位置給旋轉回來,然后再減去 x 和 y 去得到真正的 width 和 height。

看看代碼

class Graph {
  // ...

  // 根據縮放點更新圖形
  movePoint(type, newPos, oldBox) {
    // 1. 計算 width 和 height
    // 計算縮放中心(也就是矩形的中點)
    const cx = oldBox.x + oldBox.width / 2;
    const cy = oldBox.y + oldBox.height / 2;

    // 計算反向旋轉的光標位置
    const { x: posX, y: poxY } = transformRotate(
      newPos.x,
      newPos.y,
      -(oldBox.rotation || 0), // 注意這里是負數
      cx,
      cy
    );
    
    let width = 0;
    let height = 0;
    if (type === 'se') {
      // 參照點為左上角(x 和 y)
      // 新的寬高自然就是光標位置減去 x、y
      width = posX - oldBox.x;
      height = poxY - oldBox.y;
    }
    // 其他控制點的邏輯暫且省略...
    
    // 2. 計算 x 和 y
    // ...
  }
}

看看只更新寬高的效果。

可以看到是有問題的,因為修改寬高后,矩形的中心點也發生了變化,導致縮放中心錯誤。所以我們要修正一下 x 和 y。

修正 x 和 y

接著我們就要修正 x 和 y 的值。

重點就一句話:縮放前的參考點和縮放后的參考點的位置要保持一致。這個參考點其實就是圖形縮放過程中的縮放中心。

對于右下角縮放控制點,它的縮放中心就是左上角,即 x 和 y 經過旋轉的位置。

class Graph {
  // ...

  movePoint(type, newPos, oldBox) {
    // 1. 計算 width 和 height
    // ...
    

    // 2. 計算 x 和 y

    // 設置參照點,不同縮放類型的參照點不同
    let prevOriginX = 0;
    let prevOriginY = 0;
    let originX = 0;
    let originY = 0;
    if (type === "se") {
      prevOriginX = oldBox.x;
      prevOriginY = oldBox.y;
      originX = oldBox.x;
      originY = oldBox.y;
    }
    // 其他縮放類型暫且省略

    // 縮放前的參考點位置
    const { x: prevRotatedOriginX, y: prevRotatedOriginY } = transformRotate(
      prevOriginX,
      prevOriginY,
      oldBox.rotation || 0,
      cx,
      cy
    );
    // 縮放后的參考點位置
    const { x: rotatedOriginX, y: rotatedOriginY } = transformRotate(
      originX,
      originY,
      oldBox.rotation || 0,
      oldBox.x + width / 2, // 旋轉中心是新的
      oldBox.y + height / 2
    );
    // 計算新舊兩個參考點的差值,對 x、y 進行補正
    const dx = rotatedOriginX - prevRotatedOriginX;
    const dy = rotatedOriginY - prevRotatedOriginY;
    const x = oldBox.x - dx;
    const y = oldBox.y - dy;
  }
}

width 和 height 可能為負數,這里要做一個標準化,然后賦值給圖形屬性即可。

this.setAttrs(
  normalizeRect({
    x,
    y,
    width,
    height,
  }),
);

其他縮放控制點

對于其他類型縮放控制點,比如左上、右上、左下縮放控制點,它們的大框架是一樣的,只是 width 和 height 計算方式不同,以及參考點不同。

不同類型下 width 和 height 的設置:

let width = 0;
let height = 0;
if (type === 'se') { // 右下
  width = posX - oldBox.x;
  height = poxY - oldBox.y;
} else if (type === 'ne') { // 右上
  width = posX - oldBox.x;
  height = oldBox.y + oldBox.height - poxY;
} else if (type === 'nw') {
  width = oldBox.x + oldBox.width - posX;
  height = oldBox.y + oldBox.height - poxY;
} else if (type === 'sw') {
  width = oldBox.x + oldBox.width - posX;
  height = poxY - oldBox.y;
}

新舊參考點設置:

let prevOriginX = 0;
let prevOriginY = 0;
let originX = 0;
let originY = 0;
if (type === 'se') {
  prevOriginX = oldBox.x; // 右下縮放點,參考點為左上角
  prevOriginY = oldBox.y;
  originX = oldBox.x;
  originY = oldBox.y;
} else if (type === 'ne') { // 右上縮放點,參考點為左下角
  prevOriginX = oldBox.x;
  prevOriginY = oldBox.y + oldBox.height;
  originX = oldBox.x;
  originY = oldBox.y + height;
} else if (type === 'nw') {
  prevOriginX = oldBox.x + oldBox.width;
  prevOriginY = oldBox.y + oldBox.height;
  originX = oldBox.x + width;
  originY = oldBox.y + height;
} else if (type === 'sw') {
  prevOriginX = oldBox.x + oldBox.width;
  prevOriginY = oldBox.y;
  originX = oldBox.x + width;
  originY = oldBox.y;
}

暫時沒實現正北、正南、正西、正東的邏輯,邏輯大差不差。

鎖定縮放比

按住 shift 可以鎖定縮放比。

做法是對比新舊圖形寬高比,將 width 和 height 其中一個進行修正即可。注意正負號。

方法需要多傳一個 keepRatio 的參數:

class Graph {
  // ...

  movePoint(type, newPos, oldBox, keepRatio = false) {
    // 1. 計算 width 和 height
    // ...
    
    if (keepRatio) {
      const ratio = oldBox.width / oldBox.height;
      const newRatio = Math.abs(width / height);
      if (newRatio > ratio) {
        height = (Math.sign(height) * Math.abs(width)) / ratio;
      } else {
        width = Math.sign(width) * Math.abs(height) * ratio;
      }
    }
    
    // 2. 計算 x 和 y
    // ...
  }
}

貌似沒考慮除數 height 為 0 的情況..

優化點

本文的實現是考慮的是比較簡單的縮放圖形場景,一些更復雜的場景并未實現。

縮放還有另一種策略,就是會產生 反向顛倒 的縮放。要實現這個效果,需要引入縮放屬性,復雜度會提升很多。

另外就是選中多個圖形,然后縮放的場景我沒實現。這種場景下,通常是要鎖定寬高比的。

否則就會出現圖形的斜切效果,這個如果要實現,我們還要引入斜切屬性,復雜度再一次提升。

下面是 Figma 的效果,真是讓人頭扁。

按住 Alt 實現圖形中心縮放也沒做,這個比較簡單,有空再做。

讀者如果看懂我這篇文章,心里應該有思路的:width、height 的計算要加入圖形中點參數,參照點設置為圖形中點。

責任編輯:姜華 來源: 前端西瓜哥
相關推薦

2023-09-26 07:39:21

2023-07-07 13:56:01

圖形編輯器畫布縮放

2024-01-03 08:43:17

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

2023-09-07 08:24:35

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

2023-08-31 11:32:57

圖形編輯器contain

2023-04-07 08:02:30

圖形編輯器對齊功能

2023-02-01 09:21:59

圖形編輯器標尺

2024-01-08 08:30:05

光標圖形編輯器開發游標

2023-09-11 09:02:31

圖形編輯器模塊間的通信

2023-04-10 08:45:44

圖形編輯器排列移動功能

2023-07-31 08:46:07

圖形編輯器圖形自動對齊

2023-08-28 08:10:50

Hex圖形編輯器

2023-10-10 16:04:30

圖形編輯器格式轉換

2023-10-08 08:11:40

圖形編輯器快捷鍵操作

2023-02-09 07:02:30

圖形編輯器修改圖形

2023-02-06 16:59:57

Canvas編輯器

2023-01-18 08:30:40

圖形編輯器元素

2023-02-02 14:07:00

圖形編輯器Canvas

2023-10-20 08:02:25

圖形編輯器前端

2023-05-09 08:15:32

圖形編輯器撤銷重做功能
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久久久久久久精 | 五月免费视频 | 日韩精品在线观看一区二区三区 | 欧美精品免费观看二区 | 国产91综合一区在线观看 | 草草在线观看 | 久久久tv | 精品一级| 成年人在线观看视频 | 日本在线看片 | 欧美日韩国产高清 | 91精品国产色综合久久 | aaa在线观看 | 国产在线一区二区 | 亚洲免费在线播放 | 久久久久av | 免费性视频 | 亚洲国产在 | 欧美电影一区 | 玖玖视频网 | 影音先锋成人资源 | 久久亚洲天堂 | 欧美国产日韩成人 | 精品九九在线 | 精品一区二区久久久久久久网站 | 涩涩导航 | 最新av在线网址 | 91精品久久久久久久久久小网站 | 日本三级播放 | 91久久精品国产91久久性色tv | 国产成人精品免费视频 | 日本大香伊一区二区三区 | 久久国产精品一区二区 | 久久久久国产精品一区二区 | 成人精品鲁一区一区二区 | 成年人黄色小视频 | 久久精品国产一区二区电影 | 亚洲精品一区在线观看 | 亚洲一区二区视频 | 国产精品99一区二区 | 日韩av大片免费看 |