AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

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

來源: 責編: 時間:2023-10-20 10:02:58 350觀看
導讀編輯器 github 地址:https://github.com/F-star/suika線上體驗:https://blog.fstars.wang/app/suika/圖形的屬性圖形有幾個重要的基礎屬性,會經常被用到,我們在實現縮放圖形前需要理清一下它們。x / ywidth / heightrotat

IK328資訊網——每日最新資訊28at.com

編輯器 github 地址:IK328資訊網——每日最新資訊28at.com

https://github.com/F-star/suikaIK328資訊網——每日最新資訊28at.com

線上體驗:IK328資訊網——每日最新資訊28at.com

https://blog.fstars.wang/app/suika/IK328資訊網——每日最新資訊28at.com

圖形的屬性

圖形有幾個重要的基礎屬性,會經常被用到,我們在實現縮放圖形前需要理清一下它們。IK328資訊網——每日最新資訊28at.com

  • x / y
  • width / height
  • rotation

位置和大小

x 和 y 為圖形的左上角位置,注意是旋轉前的。IK328資訊網——每日最新資訊28at.com

x、y 旋轉后我們叫做 rotatedX、rotatedY,屬性面板中會用到。IK328資訊網——每日最新資訊28at.com

width 和 height 為圖形的寬高,這個沒什么好說的。IK328資訊網——每日最新資訊28at.com

另外,有些圖形有些特殊,它的 x、y、width、height 是要通過其他屬性計算出來的,比如貝塞爾曲線。IK328資訊網——每日最新資訊28at.com

旋轉

rotation 為圖形的旋轉度數,通常使用 弧度單位。IK328資訊網——每日最新資訊28at.com

因為弧度是數學計算中的常客,各種 API 都是要求提供弧度的,比如內置的 Math.sin() 方法。IK328資訊網——每日最新資訊28at.com

你存角度自然也是可以,但不推薦,但計算時多了一層多余的單位轉換,且丟失一些微小的精度。IK328資訊網——每日最新資訊28at.com

當然 UI 層還是要展示角度,因為是面向用戶的,對于數據和 UI 不統一的問題,在 UI 層做一個轉換即可。IK328資訊網——每日最新資訊28at.com

旋轉度數通常要配合一個變換中心(origin),這個可以作為一個屬性讓用戶設置。IK328資訊網——每日最新資訊28at.com

但我更建議將 x、y、width、height 形成的 矩形的中點 作為旋轉中心,這樣更簡單一些,減少用戶的心智負擔,也防止出現用戶設置一些奇怪 origin 的場景。IK328資訊網——每日最新資訊28at.com

下圖中,紅色矩形是藍色矩陣順時針旋轉 45 度得到。IK328資訊網——每日最新資訊28at.com

IK328資訊網——每日最新資訊28at.com

旋轉度數還要考慮 旋轉方向、基準角度、取值范圍 問題。IK328資訊網——每日最新資訊28at.com

(因為弧度不直觀,后面會用角度來描述,但數據層依舊還是用的弧度)IK328資訊網——每日最新資訊28at.com

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

通常這些編輯器自己決定就好。像我的項目,向上表示 0 度,順時針方向為旋轉方向,方向取值為 [0, 360)。IK328資訊網——每日最新資訊28at.com

一些編輯器是支持用戶自己設置的,比如 AutoCAD 可通過圖形單位命令,設置旋轉方向和基準角度。IK328資訊網——每日最新資訊28at.com

圖片IK328資訊網——每日最新資訊28at.com

縮放實現思路

進入正題,對圖形進行縮放。IK328資訊網——每日最新資訊28at.com

接下來會以通過右下角(也叫東南 se 方向) 縮放控制點縮放為例進行講解。IK328資訊網——每日最新資訊28at.com

IK328資訊網——每日最新資訊28at.com

交互邏輯:IK328資訊網——每日最新資訊28at.com

選擇工具下,當光標落在右下角的縮放控制點上時,光標會變成縮放樣式(這個不是本文核心,不講)。IK328資訊網——每日最新資訊28at.com

此時按下鼠標,然后進行拖拽,即可對圖形以左上角為縮放中心,進行縮放。IK328資訊網——每日最新資訊28at.com

實現思路:更新 width 和 height,然后確定參照點,修正 x  和 y。IK328資訊網——每日最新資訊28at.com

按下鼠標時,我們要把當前圖形的 x、y、width、height、rotation 記錄下來。之后的縮放是基于這個初始狀態進行的。IK328資訊網——每日最新資訊28at.com

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

拖拽時,調用我們將要實現的 movePoint 方法,去更新這個圖形。IK328資訊網——每日最新資訊28at.com

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

下面就是核心方法 movePoint 的實現邏輯了。IK328資訊網——每日最新資訊28at.com

更新 width 和 height

首先是更新矩形寬高。IK328資訊網——每日最新資訊28at.com

因為有一個旋轉,所以算法不會這么直觀。IK328資訊網——每日最新資訊28at.com

我們要意識到這里有一個變換。看到的圖形,是做過變換(基于矩形中心旋轉)之后的,但我們需要修改的 width、height、x、y 則是旋轉前的。IK328資訊網——每日最新資訊28at.com

所以我們需要把光標位置給旋轉回來,然后再減去 x 和 y 去得到真正的 width 和 height。IK328資訊網——每日最新資訊28at.com

IK328資訊網——每日最新資訊28at.com

看看代碼IK328資訊網——每日最新資訊28at.com

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    // ...  }}

看看只更新寬高的效果。IK328資訊網——每日最新資訊28at.com

IK328資訊網——每日最新資訊28at.com

可以看到是有問題的,因為修改寬高后,矩形的中心點也發生了變化,導致縮放中心錯誤。所以我們要修正一下 x 和 y。IK328資訊網——每日最新資訊28at.com

修正 x 和 y

接著我們就要修正 x 和 y 的值。IK328資訊網——每日最新資訊28at.com

重點就一句話:縮放前的參考點和縮放后的參考點的位置要保持一致。這個參考點其實就是圖形縮放過程中的縮放中心。IK328資訊網——每日最新資訊28at.com

對于右下角縮放控制點,它的縮放中心就是左上角,即 x 和 y 經過旋轉的位置。IK328資訊網——每日最新資訊28at.com

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 可能為負數,這里要做一個標準化,然后賦值給圖形屬性即可。IK328資訊網——每日最新資訊28at.com

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

其他縮放控制點

對于其他類型縮放控制點,比如左上、右上、左下縮放控制點,它們的大框架是一樣的,只是 width 和 height 計算方式不同,以及參考點不同。IK328資訊網——每日最新資訊28at.com

不同類型下 width 和 height 的設置:IK328資訊網——每日最新資訊28at.com

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

新舊參考點設置:IK328資訊網——每日最新資訊28at.com

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

暫時沒實現正北、正南、正西、正東的邏輯,邏輯大差不差。IK328資訊網——每日最新資訊28at.com

鎖定縮放比

按住 shift 可以鎖定縮放比。IK328資訊網——每日最新資訊28at.com

做法是對比新舊圖形寬高比,將 width 和 height 其中一個進行修正即可。注意正負號。IK328資訊網——每日最新資訊28at.com

方法需要多傳一個 keepRatio 的參數:IK328資訊網——每日最新資訊28at.com

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 的情況..IK328資訊網——每日最新資訊28at.com

優化點

本文的實現是考慮的是比較簡單的縮放圖形場景,一些更復雜的場景并未實現。IK328資訊網——每日最新資訊28at.com

縮放還有另一種策略,就是會產生 反向顛倒 的縮放。要實現這個效果,需要引入縮放屬性,復雜度會提升很多。IK328資訊網——每日最新資訊28at.com

另外就是選中多個圖形,然后縮放的場景我沒實現。這種場景下,通常是要鎖定寬高比的。IK328資訊網——每日最新資訊28at.com

否則就會出現圖形的斜切效果,這個如果要實現,我們還要引入斜切屬性,復雜度再一次提升。IK328資訊網——每日最新資訊28at.com

下面是 Figma 的效果,真是讓人頭扁。IK328資訊網——每日最新資訊28at.com

IK328資訊網——每日最新資訊28at.com

按住 Alt 實現圖形中心縮放也沒做,這個比較簡單,有空再做。IK328資訊網——每日最新資訊28at.com

讀者如果看懂我這篇文章,心里應該有思路的:width、height 的計算要加入圖形中點參數,參照點設置為圖形中點。IK328資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-14348-0.html圖形編輯器開發:實現縮放圖形

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 深入理解 Netty FastThreadLocal

下一篇: 國際權威大獎GCAs揭曉 秦淮數據包攬數據中心類兩項大獎

標簽:
  • 熱門焦點
Top