新聞中心
大家好,我是前端西瓜哥。

畫布縮放是圖形設(shè)計工具中很重要的基礎(chǔ)能力。
通過它,我們可以像舉著一臺攝影機,在圖形所在的世界到處游逛,透過鏡頭,可以只看自己想看的圖形;可以拉近攝影機,看到圖形的細節(jié);也可以拉遠攝影機,總覽多個圖形之間的關(guān)系。
ok,那么我們看看如何實現(xiàn)縮放畫布功能。
文中的動圖演示來自我正在開發(fā)的圖形設(shè)計工具:
https://github.com/F-star/suika
線上體驗:
https://blog.fstars.wang/app/suika/
場景坐標系和視圖坐標系
場景坐標系 就是圖形所在的二維平面世界所使用的坐標系。單位是像素(px)。坐標系的原點在畫布(canvas 元素)的左上角,x 軸向右,y 軸向下。
圖形會被繪制到這個平面,理論上它的范圍是可以 無限延展 的。(不過實際上我們會給一個上限,但這個值也非常大。無限大的話沒有意義,且浮點數(shù)是有取值范圍的)
然而顯示器的寬高是有限的,只能看一個矩形范圍內(nèi)的內(nèi)容。
所以我們需要引入一個 “攝影機”:視圖坐標系,只看部分的區(qū)域。
其實就是將原來真實的圖形的坐標做一個線性計算轉(zhuǎn)換。
首先是將特定區(qū)域 移動 到視口中,就像攝影機從原點移動我們想要觀察的某個物體上。不過實際上是物體所在的平面做了一個方向的移動。
然后再做一個縮放,就像攝影機拉近或遠離與目標物體距離,效果是物體在鏡頭下變大或變小。
轉(zhuǎn)換就兩步,移動然后縮放。
視圖矩陣轉(zhuǎn)換
場景坐標系到視圖坐標系的轉(zhuǎn)換,我們通過 視圖矩陣 相乘來實現(xiàn)。
事實上,任意兩個坐標系下坐標的轉(zhuǎn)換,都可以通過一個矩陣乘法來實現(xiàn)。
首先是將坐標進行位移,x 方向位移 -viewport.x,y 方向位移 -viewport.y。這里是負數(shù),雖然我們想要移動 “攝影機”這是因為移動的是畫布
<平移矩陣> * 坐標然后再縮放(縮放值我們會用 zoom 表示):
<縮放矩陣> * 平移后的坐標所有過程寫在一起,就是:
<縮放矩陣> * <平移矩陣> * 坐標矩陣乘法符合結(jié)合律,所以我們的視圖矩陣為:
<視圖矩陣>
= <縮放矩陣> * <平移矩陣>矩陣表示為:
計算結(jié)果為:
對應(yīng)的 Canvas 2D 代碼:
ctx.scale(zoom, zoom);
ctx.translate(-viewport.x, -viewport.y);寫成一個方法:
// 場景坐標轉(zhuǎn)視圖坐標
function sceneCoordsToViewport(x, y, zoom, scrollX, scrollY) {
return {
x: (x - scrollX) * zoom,
y: (y - scrollY) * zoom
};
}至于反過來,場景坐標系轉(zhuǎn)視圖坐標,計算它的逆矩陣即可:
以光標為中心縮放
首先我們來認清本質(zhì),所謂以光標為中心縮放,不變的是什么?
光標所在點在視圖坐標系距離視口左上角的相對位置,保持不變。
我們要做的事是,在 zoom 變化后,調(diào)整 viewport.x 和 viewport.y 的值,讓光標在視圖坐標系上相對視口左上角距離不變。
這里得補充一個知識點。就是兩個坐標系中距離的轉(zhuǎn)換:
- 場景轉(zhuǎn)視圖,距離轉(zhuǎn)換為 dist * zoom;
- 視圖轉(zhuǎn)場景,距離的轉(zhuǎn)換是 dist / zoom,因為視口看到的圖形都是縮放(乘以 zoom)后的結(jié)果,所以反過來就要除回去。
實現(xiàn)思路是:
- 記錄好縮放前,光標所在位置的場景坐標;
- 計算 (cx, cy) 在舊縮放比(zoom)的場景坐標。
- 計算 cx 在新的縮放比(zoom)下,(cx / zoom, cy / zoom)。
- 然后二者相減,即可得到新的適口左上角坐標。
代碼實現(xiàn)為:
/**
* 以某點為中心,進行畫布縮放
* @param {number} zoom 新的縮放比
* @param {number} cx 縮放中心(使用視圖坐標)
* @param {number} cy
*/
const setZoomAndUpdateViewport = (zoom, cx, cy) => {
const prevZoom = this.zoom;
this.zoom = zoom;
// 計算縮放中點的場景坐標
const { x: sceneCX, y: sceneCY } = viewportCoordsToScene(
cx,
cy,
prevZoom,
this.viewport.x,
this.viewport.y,
);
// 核心代碼
const newViewportX = sceneCX - cx / zoom;
const newViewportY = sceneCY - cy / zoom;
this.viewport.x = newViewportX;
this.viewport.y = newViewportY;
this.renderScene();
};以畫布為中心進行縮放
如果縮放時光標不在畫布上,比如通過手動輸入縮放值時,會 以畫布的中心位置進行縮放。
實現(xiàn)同上,只是 cx 和 cy 改成傳入視口(即畫布)的寬高除以 2:(viewport.width / 2, , (viewport.height / 2)。
結(jié)尾
要實現(xiàn)畫布縮放,重點是理解場景坐標和視圖坐標之間的關(guān)系。
場景坐標轉(zhuǎn)視圖坐標,首先需要將畫布進行移動,讓場景坐標的原點和視圖坐標的原點對上(場景坐標移動 -viewport.x 和 -viewport.x),然后再進行縮放(乘以 zoom)。
網(wǎng)頁標題:圖形編輯器開發(fā):以光標為中心縮放畫布
當前網(wǎng)址:http://www.dlmjj.cn/article/cdocdgc.html


咨詢
建站咨詢
