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

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

開發 前端
旋轉度數通常要配合一個變換中心(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-10-10 16:04:30

圖形編輯器格式轉換

2023-10-08 08:11:40

圖形編輯器快捷鍵操作

2023-08-28 08:10:50

Hex圖形編輯器

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技術棧公眾號

久久天堂久久| 欧美视频xxxx| av在线亚洲色图| 欧美日韩国产一中文字不卡| 欧美一级二级三级| 亚洲影视一区二区| 亚洲美女91| 国产午夜精品一区理论片飘花| 激情 小说 亚洲 图片: 伦| 老司机精品影院| 国产精选一区二区三区| 欧美专区中文字幕| 午夜剧场免费在线观看| 色哟哟精品丝袜一区二区| 欧美日韩的一区二区| 色欲色香天天天综合网www| 丁香婷婷在线| 丁香啪啪综合成人亚洲小说| 国产精品草莓在线免费观看 | 手机视频在线观看| 日本不卡影院| 国产精品国产三级国产三级人妇| 范冰冰一级做a爰片久久毛片| 日本中文字幕精品—区二区| 色综合91久久精品中文字幕| 久久亚洲一区二区三区明星换脸 | 日韩欧美在线第一页| 国产日产精品一区二区三区四区的观看方式| 国产午夜福利视频在线观看| 国产视频亚洲精品| 久久婷婷丁香| 欧美日韩一区二区精品| aaa免费在线观看| 青青青草网站免费视频在线观看| 国产一区二区伦理| 中文字幕精品网| 99在线精品免费| 青青久在线视频免费观看| 自拍日韩亚洲一区在线| 精品欧美一区二区久久| 亚洲美洲欧洲综合国产一区| 国产成人av免费看| 日韩av中文字幕第一页| 亚洲一区二区久久久| 国内精品免费**视频| 狂野欧美性猛交xxxxx视频| 大桥未久恸哭の女教师| 亚洲欧洲性图库| 国产麻豆一区二区三区| 久久99久久久久久| 亚洲成人av片在线观看| 性一交一乱一区二区洋洋av| 懂色av成人一区二区三区| 三年中国中文在线观看免费播放| 欧美精品成人一区二区三区四区| 成人激情开心网| 伊人久久成人网| 久久99热精品| 国产一区二三区| 爱情岛论坛亚洲品质自拍视频网站| 免费黄色av网址| 欧美亚洲一区二区三区四区| 成人在线国产| 涩涩视频在线观看免费| 精品这里只有精品| 亚洲福利视频网站| 欧美日一区二区三区在线观看国产免| 国产精品国产三级国产普通话对白| 中文字幕精品—区二区日日骚| 日本丰满少妇黄大片在线观看| 精品久久在线观看| 国产精品123| 99re在线播放| 国产91久久久| 91丝袜美腿高跟国产极品老师 | 亚洲精品在线观| 在线观看国产三级| 国产剧情一区| 久久精品亚洲精品| 精品无码av在线| 黄色成人在线视频| 精品视频一区 二区 三区| xxxx在线免费观看| 哺乳挤奶一区二区三区免费看| 亚洲国产精品久久91精品| 国内精品久久99人妻无码| 91精品美女| 欧美精品免费视频| 国产一级免费片| 国产一区二区精品福利地址| 久久精品国产亚洲精品| 国产午夜视频在线| 久久一区国产| 亚洲一区二区三区四区视频| 手机看片一区二区| 麻豆极品一区二区三区| 91九色蝌蚪成人| 国产精品尤物视频| 国产午夜久久| 国产乱肥老妇国产一区二| 俄罗斯嫩小性bbwbbw| 国产三级久久久| 激情五月六月婷婷| se01亚洲视频| 精品少妇一区二区三区免费观看| 人妻少妇无码精品视频区| 欧美成人综合| 国产激情999| 动漫av一区二区三区| 国产精品久久久久久久久久久免费看 | 欧美女王vk| 欧美激情视频网| 国产馆在线观看| 黄色在线成人| 国产日产久久高清欧美一区| 天天干,天天干| 国产91综合网| 亚洲精品国产一区| 亚洲人成在线网站| 精品剧情在线观看| 国产三级精品三级观看| 久久一区精品| 精品不卡一区二区三区| 91一区二区三区在线| 色欧美88888久久久久久影院| jjzz黄色片| 在线中文一区| 久久久极品av| 日本视频免费观看| 99久久99久久精品免费看蜜桃| 中文字幕乱码免费| 2019中文亚洲字幕| 在线a欧美视频| 日韩欧美在线观看免费| 97久久精品人人澡人人爽| 91黄色在线看| 视频国产精品| 欧美日韩高清区| 国产不卡精品视频| 一区二区三区四区在线免费观看| 视频二区在线播放| 清纯唯美综合亚洲| 国产精品久久久久久久美男| 黄色av网站在线| 色呦呦日韩精品| 熟女俱乐部一区二区视频在线| 99精品热视频只有精品10| 国产精品99久久久久久久| 黄色网页网址在线免费| 91麻豆精品国产自产在线观看一区 | 国产毛片久久久久久| 99久久亚洲精品| 久久久精品中文字幕| 91精品国产乱码久久久| 日韩一区日韩二区| 免费网站在线观看黄| 99精品美女| 91超碰在线免费观看| 色女人在线视频| 欧美精品一区二区在线播放| 日韩三级免费看| 久久婷婷综合激情| 日本成人中文字幕在线| 成人影视亚洲图片在线| 成人久久精品视频| 久久不射影院| 精品一区电影国产| 免费在线不卡av| 亚洲天天做日日做天天谢日日欢 | av电影天堂一区二区在线观看| 男女激情无遮挡| 国产成人高清| 成人有码在线视频| 黄色成人在线网| 精品视频偷偷看在线观看| 波多野结衣午夜| 日韩理论在线观看| 日韩精品人妻中文字幕有码| 蜜乳av另类精品一区二区| 日本一区二区三区四区在线观看| 四虎永久精品在线| 欧美激情精品久久久久久蜜臀| 天堂成人在线观看| 欧美午夜精品免费| 久久久久久久久久综合| 久久一日本道色综合| 天堂av8在线| 99成人在线| 亚洲欧美日韩精品久久久 | 欧美日韩女优| 九九视频这里只有精品| 久久天堂电影| 日韩午夜精品视频| 久久久精品成人| 国产乱码字幕精品高清av| 精品无码一区二区三区在线| 日韩中文在线电影| 日韩免费视频在线观看| 欧美13一16娇小xxxx| 色诱视频网站一区| 欧美精品乱码视频一二专区| 久久久91精品国产一区二区精品| 亚洲精品永久视频| 老鸭窝91久久精品色噜噜导演| 国产高清免费在线| 国产欧美一区二区三区精品观看 | 欧美少妇xxxx| 国产精品久久亚洲7777| 亚洲美女色播| 国产成人精品亚洲精品| yellow在线观看网址| 日韩三级视频在线看| 国产成人一级片| 26uuu亚洲综合色欧美| 国内自拍第二页| 久久深夜福利| 日本少妇高潮喷水视频| 中文精品久久| 亚洲欧美日韩精品综合在线观看| 亚洲三级精品| 国产视频一区二区三区四区| 电影一区中文字幕| 国产精品综合网站| 电影亚洲一区| 青青草国产精品一区二区| 黄网av在线| 美女福利精品视频| 麻豆tv免费在线观看| 国产亚洲欧美aaaa| 蜜桃视频在线入口www| 亚洲福利在线看| 亚洲精品国产精品乱码不卡| 亚洲.国产.中文慕字在线| 成年人二级毛片| 欧美国产视频在线| 欧美日韩高清丝袜| 久久久精品人体av艺术| 亚洲国产精品成人综合久久久| 成人黄色小视频在线观看| 永久av免费在线观看| 国产在线播放一区三区四| 不用播放器的免费av| 精品一区二区国语对白| 911福利视频| 韩国欧美国产1区| www.88av| 香蕉久久久久久久| yy6080午夜| 国产麻豆精品| 国产女同一区二区| 九九热这里有精品| 国产精品美女久久| 欧美91在线|欧美| 国产日韩中文字幕| 亚洲老司机网| 97夜夜澡人人双人人人喊| 日韩精品中文字幕一区二区| av在线不卡观看| 国产福利资源一区| 久久久久久高清| 岳的好大精品一区二区三区| 人禽交欧美网站免费| av一区二区高清| 中文字幕欧美日韩一区二区三区| 91成人网在线观看| 国产一区二区三区乱码| 国产视频一区三区| www.天天射.com| 国产在线精品一区二区夜色| 国产精品二区视频| 99久久伊人久久99| 欧洲美熟女乱又伦| 综合在线观看色| 国产一级视频在线播放| 欧美日韩国产丝袜另类| 中文字幕 自拍偷拍| 91精品国产综合久久久久久久久久 | 色偷偷综合网| 亚洲色图都市激情| 国产日韩亚洲| 在线观看免费黄网站| 国产美女久久久久| 亚洲国产精品自拍视频| 中文字幕欧美三区| 精品无码av在线| 色婷婷久久久久swag精品| 国产精品女同一区二区| 亚洲精品国产品国语在线| 国产精品久久久久久久龚玥菲 | 亚洲欧美日韩精品久久久| 欧美成人69| 一本久道综合色婷婷五月| 国产一区二区三区四区在线观看| 在线免费观看a级片| 国产精品福利一区二区三区| 日韩黄色a级片| 欧美日韩久久久一区| 天堂av资源在线| 欧美精品免费在线| 欧美xnxx| 国产一级精品aaaaa看| 爽成人777777婷婷| 欧美成人xxxxx| 国产乱妇无码大片在线观看| 西西444www无码大胆| 亚洲综合免费观看高清在线观看| 成人三级做爰av| 久久久国产精品午夜一区ai换脸| 欧美极品视频在线观看| 欧美日韩精品一区二区| 视频在线不卡| 欧美日本亚洲视频| 亚洲精品成a人ⅴ香蕉片| 欧美精品免费观看二区| 怡红院精品视频在线观看极品| 天天操狠狠操夜夜操| 久久一区二区视频| 亚洲国产成人精品激情在线| 56国语精品自产拍在线观看| 九色视频在线播放| 91国产精品电影| 成人高潮视频| 日韩一二区视频| 激情综合色播激情啊| 五月激情四射婷婷| 欧美性生交大片免费| 欧美一级在线免费观看| 欧美成人国产va精品日本一级| 成人国产精品一区二区免费麻豆 | 精品日韩美女的视频高清| 国产成人三级在线播放| 久久精品人人做人人爽| 成人午夜sm精品久久久久久久| 欧美精品七区| 午夜一级在线看亚洲| 黄色国产在线观看| 五月天久久比比资源色| 色婷婷在线视频| 国产69精品99久久久久久宅男| 91精品国产自产在线丝袜啪| 伊人网在线免费| 国产成人自拍高清视频在线免费播放| 暗呦丨小u女国产精品| 欧美美女一区二区三区| 毛片免费不卡| 91九色视频导航| 国产精品久久久久久久久久辛辛| 亚洲 日韩 国产第一区| 日韩精品视频网| a资源在线观看| 欧美挠脚心视频网站| 黄色在线播放网站| 91在线视频导航| 欧美日本一区二区高清播放视频| 人妻精油按摩bd高清中文字幕| 伊人色综合久久天天人手人婷| www.av黄色| 性欧美办公室18xxxxhd| 成人国产一区二区三区精品麻豆| 欧日韩一区二区三区| 免费成人在线观看视频| 在线免费看av网站| 欧美va日韩va| 中文字幕色婷婷在线视频| 日韩精品久久久毛片一区二区| 一区二区三区在线电影| 成年人性生活视频| 亚洲一区免费视频| 亚洲 欧美 自拍偷拍| xx视频.9999.com| 国产精品美女久久久久人| 国产精品久久久久久久久电影网| 日本美女视频一区二区| 亚洲丝袜在线观看| 午夜影院久久久| 美女欧美视频在线观看免费 | 久久的精品视频| 99re热精品视频| 国产精品第12页| 日韩久久一区二区| 五月婷婷开心中文字幕| 国产精品狼人色视频一区| 欧美成人午夜| 91网站免费视频| 欧美一级在线免费| 国产免费拔擦拔擦8x高清在线人| 天堂资源在线亚洲视频| 国产成人精品影视| 91麻豆精品在线| 欧美日韩福利电影| 日本大胆欧美| 国产清纯白嫩初高中在线观看性色| 在线观看国产91| 后进极品白嫩翘臀在线播放| 日日噜噜噜噜夜夜爽亚洲精品| 国产91精品一区二区| 亚洲av人无码激艳猛片服务器| 欧美丰满少妇xxxxx做受| 欧美日韩国产一区二区三区不卡| 中文字幕18页|