日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁(yè) > 科技  > 軟件

圖形編輯器開發(fā):實(shí)現(xiàn)縮放圖形

來(lái)源: 責(zé)編: 時(shí)間:2023-10-20 10:02:58 406觀看
導(dǎo)讀編輯器 github 地址:https://github.com/F-star/suika線上體驗(yàn):https://blog.fstars.wang/app/suika/圖形的屬性圖形有幾個(gè)重要的基礎(chǔ)屬性,會(huì)經(jīng)常被用到,我們?cè)趯?shí)現(xiàn)縮放圖形前需要理清一下它們。x / ywidth / heightrotat

DNZ28資訊網(wǎng)——每日最新資訊28at.com

編輯器 github 地址:DNZ28資訊網(wǎng)——每日最新資訊28at.com

https://github.com/F-star/suikaDNZ28資訊網(wǎng)——每日最新資訊28at.com

線上體驗(yàn):DNZ28資訊網(wǎng)——每日最新資訊28at.com

https://blog.fstars.wang/app/suika/DNZ28資訊網(wǎng)——每日最新資訊28at.com

圖形的屬性

圖形有幾個(gè)重要的基礎(chǔ)屬性,會(huì)經(jīng)常被用到,我們?cè)趯?shí)現(xiàn)縮放圖形前需要理清一下它們。DNZ28資訊網(wǎng)——每日最新資訊28at.com

  • x / y
  • width / height
  • rotation

位置和大小

x 和 y 為圖形的左上角位置,注意是旋轉(zhuǎn)前的。DNZ28資訊網(wǎng)——每日最新資訊28at.com

x、y 旋轉(zhuǎn)后我們叫做 rotatedX、rotatedY,屬性面板中會(huì)用到。DNZ28資訊網(wǎng)——每日最新資訊28at.com

width 和 height 為圖形的寬高,這個(gè)沒(méi)什么好說(shuō)的。DNZ28資訊網(wǎng)——每日最新資訊28at.com

另外,有些圖形有些特殊,它的 x、y、width、height 是要通過(guò)其他屬性計(jì)算出來(lái)的,比如貝塞爾曲線。DNZ28資訊網(wǎng)——每日最新資訊28at.com

旋轉(zhuǎn)

rotation 為圖形的旋轉(zhuǎn)度數(shù),通常使用 弧度單位。DNZ28資訊網(wǎng)——每日最新資訊28at.com

因?yàn)榛《仁菙?shù)學(xué)計(jì)算中的常客,各種 API 都是要求提供弧度的,比如內(nèi)置的 Math.sin() 方法。DNZ28資訊網(wǎng)——每日最新資訊28at.com

你存角度自然也是可以,但不推薦,但計(jì)算時(shí)多了一層多余的單位轉(zhuǎn)換,且丟失一些微小的精度。DNZ28資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然 UI 層還是要展示角度,因?yàn)槭敲嫦蛴脩舻模瑢?duì)于數(shù)據(jù)和 UI 不統(tǒng)一的問(wèn)題,在 UI 層做一個(gè)轉(zhuǎn)換即可。DNZ28資訊網(wǎng)——每日最新資訊28at.com

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

但我更建議將 x、y、width、height 形成的 矩形的中點(diǎn) 作為旋轉(zhuǎn)中心,這樣更簡(jiǎn)單一些,減少用戶的心智負(fù)擔(dān),也防止出現(xiàn)用戶設(shè)置一些奇怪 origin 的場(chǎng)景。DNZ28資訊網(wǎng)——每日最新資訊28at.com

下圖中,紅色矩形是藍(lán)色矩陣順時(shí)針旋轉(zhuǎn) 45 度得到。DNZ28資訊網(wǎng)——每日最新資訊28at.com

DNZ28資訊網(wǎng)——每日最新資訊28at.com

旋轉(zhuǎn)度數(shù)還要考慮 旋轉(zhuǎn)方向、基準(zhǔn)角度、取值范圍 問(wèn)題。DNZ28資訊網(wǎng)——每日最新資訊28at.com

(因?yàn)榛《炔恢庇^,后面會(huì)用角度來(lái)描述,但數(shù)據(jù)層依舊還是用的弧度)DNZ28資訊網(wǎng)——每日最新資訊28at.com

  • 旋轉(zhuǎn)方向:設(shè)置旋轉(zhuǎn)后,圖形是會(huì)往順時(shí)針?lè)较蜻€是逆時(shí)針?lè)较蛐D(zhuǎn)。
  • 基準(zhǔn)角度:朝向哪里是 0 度。
  • 取值范圍:通常為 [0, 360) 和 (-180, 180]。二者其實(shí)等價(jià),只是顯示有區(qū)別,后者其實(shí)只是前者減去 180 度。

通常這些編輯器自己決定就好。像我的項(xiàng)目,向上表示 0 度,順時(shí)針?lè)较驗(yàn)樾D(zhuǎn)方向,方向取值為 [0, 360)。DNZ28資訊網(wǎng)——每日最新資訊28at.com

一些編輯器是支持用戶自己設(shè)置的,比如 AutoCAD 可通過(guò)圖形單位命令,設(shè)置旋轉(zhuǎn)方向和基準(zhǔn)角度。DNZ28資訊網(wǎng)——每日最新資訊28at.com

圖片DNZ28資訊網(wǎng)——每日最新資訊28at.com

縮放實(shí)現(xiàn)思路

進(jìn)入正題,對(duì)圖形進(jìn)行縮放。DNZ28資訊網(wǎng)——每日最新資訊28at.com

接下來(lái)會(huì)以通過(guò)右下角(也叫東南 se 方向) 縮放控制點(diǎn)縮放為例進(jìn)行講解。DNZ28資訊網(wǎng)——每日最新資訊28at.com

DNZ28資訊網(wǎng)——每日最新資訊28at.com

交互邏輯:DNZ28資訊網(wǎng)——每日最新資訊28at.com

選擇工具下,當(dāng)光標(biāo)落在右下角的縮放控制點(diǎn)上時(shí),光標(biāo)會(huì)變成縮放樣式(這個(gè)不是本文核心,不講)。DNZ28資訊網(wǎng)——每日最新資訊28at.com

此時(shí)按下鼠標(biāo),然后進(jìn)行拖拽,即可對(duì)圖形以左上角為縮放中心,進(jìn)行縮放。DNZ28資訊網(wǎng)——每日最新資訊28at.com

實(shí)現(xiàn)思路:更新 width 和 height,然后確定參照點(diǎn),修正 x  和 y。DNZ28資訊網(wǎng)——每日最新資訊28at.com

按下鼠標(biāo)時(shí),我們要把當(dāng)前圖形的 x、y、width、height、rotation 記錄下來(lái)。之后的縮放是基于這個(gè)初始狀態(tài)進(jìn)行的。DNZ28資訊網(wǎng)——每日最新資訊28at.com

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

拖拽時(shí),調(diào)用我們將要實(shí)現(xiàn)的 movePoint 方法,去更新這個(gè)圖形。DNZ28資訊網(wǎng)——每日最新資訊28at.com

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

下面就是核心方法 movePoint 的實(shí)現(xiàn)邏輯了。DNZ28資訊網(wǎng)——每日最新資訊28at.com

更新 width 和 height

首先是更新矩形寬高。DNZ28資訊網(wǎng)——每日最新資訊28at.com

因?yàn)橛幸粋€(gè)旋轉(zhuǎn),所以算法不會(huì)這么直觀。DNZ28資訊網(wǎng)——每日最新資訊28at.com

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

所以我們需要把光標(biāo)位置給旋轉(zhuǎn)回來(lái),然后再減去 x 和 y 去得到真正的 width 和 height。DNZ28資訊網(wǎng)——每日最新資訊28at.com

DNZ28資訊網(wǎng)——每日最新資訊28at.com

看看代碼DNZ28資訊網(wǎng)——每日最新資訊28at.com

class Graph {  // ...  // 根據(jù)縮放點(diǎn)更新圖形  movePoint(type, newPos, oldBox) {    // 1. 計(jì)算 width 和 height    // 計(jì)算縮放中心(也就是矩形的中點(diǎn))    const cx = oldBox.x + oldBox.width / 2;    const cy = oldBox.y + oldBox.height / 2;    // 計(jì)算反向旋轉(zhuǎn)的光標(biāo)位置    const { x: posX, y: poxY } = transformRotate(      newPos.x,      newPos.y,      -(oldBox.rotation || 0), // 注意這里是負(fù)數(shù)      cx,      cy    );        let width = 0;    let height = 0;    if (type === 'se') {      // 參照點(diǎn)為左上角(x 和 y)      // 新的寬高自然就是光標(biāo)位置減去 x、y      width = posX - oldBox.x;      height = poxY - oldBox.y;    }    // 其他控制點(diǎn)的邏輯暫且省略...        // 2. 計(jì)算 x 和 y    // ...  }}

看看只更新寬高的效果。DNZ28資訊網(wǎng)——每日最新資訊28at.com

DNZ28資訊網(wǎng)——每日最新資訊28at.com

可以看到是有問(wèn)題的,因?yàn)樾薷膶捀吆螅匦蔚闹行狞c(diǎn)也發(fā)生了變化,導(dǎo)致縮放中心錯(cuò)誤。所以我們要修正一下 x 和 y。DNZ28資訊網(wǎng)——每日最新資訊28at.com

修正 x 和 y

接著我們就要修正 x 和 y 的值。DNZ28資訊網(wǎng)——每日最新資訊28at.com

重點(diǎn)就一句話:縮放前的參考點(diǎn)和縮放后的參考點(diǎn)的位置要保持一致。這個(gè)參考點(diǎn)其實(shí)就是圖形縮放過(guò)程中的縮放中心。DNZ28資訊網(wǎng)——每日最新資訊28at.com

對(duì)于右下角縮放控制點(diǎn),它的縮放中心就是左上角,即 x 和 y 經(jīng)過(guò)旋轉(zhuǎn)的位置。DNZ28資訊網(wǎng)——每日最新資訊28at.com

class Graph {  // ...  movePoint(type, newPos, oldBox) {    // 1. 計(jì)算 width 和 height    // ...        // 2. 計(jì)算 x 和 y    // 設(shè)置參照點(diǎn),不同縮放類型的參照點(diǎn)不同    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;    }    // 其他縮放類型暫且省略    // 縮放前的參考點(diǎn)位置    const { x: prevRotatedOriginX, y: prevRotatedOriginY } = transformRotate(      prevOriginX,      prevOriginY,      oldBox.rotation || 0,      cx,      cy    );    // 縮放后的參考點(diǎn)位置    const { x: rotatedOriginX, y: rotatedOriginY } = transformRotate(      originX,      originY,      oldBox.rotation || 0,      oldBox.x + width / 2, // 旋轉(zhuǎn)中心是新的      oldBox.y + height / 2    );    // 計(jì)算新舊兩個(gè)參考點(diǎn)的差值,對(duì) x、y 進(jìn)行補(bǔ)正    const dx = rotatedOriginX - prevRotatedOriginX;    const dy = rotatedOriginY - prevRotatedOriginY;    const x = oldBox.x - dx;    const y = oldBox.y - dy;  }}

width 和 height 可能為負(fù)數(shù),這里要做一個(gè)標(biāo)準(zhǔn)化,然后賦值給圖形屬性即可。DNZ28資訊網(wǎng)——每日最新資訊28at.com

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

其他縮放控制點(diǎn)

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

不同類型下 width 和 height 的設(shè)置:DNZ28資訊網(wǎng)——每日最新資訊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;}

新舊參考點(diǎn)設(shè)置:DNZ28資訊網(wǎng)——每日最新資訊28at.com

let prevOriginX = 0;let prevOriginY = 0;let originX = 0;let originY = 0;if (type === 'se') {  prevOriginX = oldBox.x; // 右下縮放點(diǎn),參考點(diǎn)為左上角  prevOriginY = oldBox.y;  originX = oldBox.x;  originY = oldBox.y;} else if (type === 'ne') { // 右上縮放點(diǎn),參考點(diǎn)為左下角  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;}

暫時(shí)沒(méi)實(shí)現(xiàn)正北、正南、正西、正東的邏輯,邏輯大差不差。DNZ28資訊網(wǎng)——每日最新資訊28at.com

鎖定縮放比

按住 shift 可以鎖定縮放比。DNZ28資訊網(wǎng)——每日最新資訊28at.com

做法是對(duì)比新舊圖形寬高比,將 width 和 height 其中一個(gè)進(jìn)行修正即可。注意正負(fù)號(hào)。DNZ28資訊網(wǎng)——每日最新資訊28at.com

方法需要多傳一個(gè) keepRatio 的參數(shù):DNZ28資訊網(wǎng)——每日最新資訊28at.com

class Graph {  // ...  movePoint(type, newPos, oldBox, keepRatio = false) {    // 1. 計(jì)算 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. 計(jì)算 x 和 y    // ...  }}

貌似沒(méi)考慮除數(shù) height 為 0 的情況..DNZ28資訊網(wǎng)——每日最新資訊28at.com

優(yōu)化點(diǎn)

本文的實(shí)現(xiàn)是考慮的是比較簡(jiǎn)單的縮放圖形場(chǎng)景,一些更復(fù)雜的場(chǎng)景并未實(shí)現(xiàn)。DNZ28資訊網(wǎng)——每日最新資訊28at.com

縮放還有另一種策略,就是會(huì)產(chǎn)生 反向顛倒 的縮放。要實(shí)現(xiàn)這個(gè)效果,需要引入縮放屬性,復(fù)雜度會(huì)提升很多。DNZ28資訊網(wǎng)——每日最新資訊28at.com

另外就是選中多個(gè)圖形,然后縮放的場(chǎng)景我沒(méi)實(shí)現(xiàn)。這種場(chǎng)景下,通常是要鎖定寬高比的。DNZ28資訊網(wǎng)——每日最新資訊28at.com

否則就會(huì)出現(xiàn)圖形的斜切效果,這個(gè)如果要實(shí)現(xiàn),我們還要引入斜切屬性,復(fù)雜度再一次提升。DNZ28資訊網(wǎng)——每日最新資訊28at.com

下面是 Figma 的效果,真是讓人頭扁。DNZ28資訊網(wǎng)——每日最新資訊28at.com

DNZ28資訊網(wǎng)——每日最新資訊28at.com

按住 Alt 實(shí)現(xiàn)圖形中心縮放也沒(méi)做,這個(gè)比較簡(jiǎn)單,有空再做。DNZ28資訊網(wǎng)——每日最新資訊28at.com

讀者如果看懂我這篇文章,心里應(yīng)該有思路的:width、height 的計(jì)算要加入圖形中點(diǎn)參數(shù),參照點(diǎn)設(shè)置為圖形中點(diǎn)。DNZ28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-14348-0.html圖形編輯器開發(fā):實(shí)現(xiàn)縮放圖形

聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com

上一篇: 深入理解 Netty FastThreadLocal

下一篇: 國(guó)際權(quán)威大獎(jiǎng)GCAs揭曉 秦淮數(shù)據(jù)包攬數(shù)據(jù)中心類兩項(xiàng)大獎(jiǎng)

標(biāo)簽:
  • 熱門焦點(diǎn)
Top 主站蜘蛛池模板: 武威市| 诸暨市| 阳西县| 九龙坡区| 清丰县| 习水县| 醴陵市| 方山县| 龙江县| 金沙县| 临湘市| 礼泉县| 尤溪县| 芒康县| 赤峰市| 手游| 新野县| 屯留县| 越西县| 淳化县| 邯郸市| 黄骅市| 外汇| 呼和浩特市| 保靖县| 宜宾市| 孟州市| 雷山县| 泸西县| 金川县| 九台市| 丹凤县| 铜梁县| 五台县| 互助| 含山县| 洛扎县| 芮城县| 东明县| 富宁县| 宁安市|