用canvas 實現(xiàn)矩形的移動(點、線、面)(1)


# 前言

在canvas中實現(xiàn)圖片移動、實現(xiàn)矩形移動,大家可能看的很多了。但是我為什么還要去寫這樣的一篇文章呢,因為筆者曾經(jīng)做到3維圖形下的移動。包括移動一個立方體上的一條邊線、一個面、移動多邊形的一個點。最近一直在寫canvas的相關(guān)的文章,想著復(fù)習(xí)下,讀完本篇文章你可以學(xué)到,通過移動矩形的一個點, 一個條邊線,以及整個面的移動。本篇文章從淺到深,希望你耐心讀下去。

# 面的移動

試想一下,在canvas 下實現(xiàn)移動功能。第一步肯定創(chuàng)建canvas 并對canvas 添加move 事件, 這樣我們實時獲取到我們鼠標(biāo)的位置,然后我們只需要不斷的清除畫布,然后重新畫矩形。就OK了,面的移動初步實現(xiàn)。我在下面寫點的移動會把這里重寫了, 這里先寫個快速簡易版本的。

```js

const canvas = document.getElementById('canvas');

const ctx = canvas.getContext('2d');

const width = 100;

const height = 100;

drawRect();

function drawRect(x = 10,y = 10, scale = 1) {

? ? ctx.clearRect( 0, 0, 1800, 800 );

? ? const halfwidth = width * scale / 2;

? ? const halfheight = height * scale / 2;

? ? ctx.strokeRect(x - halfwidth, y - halfheight,width * scale, height * scale)

}

let isMove =? true;

canvas.addEventListener('mousemove',(e)=>{

? ? if(!isMove) {

? ? ? ? return

? ? }

? ? const x = e.clientX;

? ? const y = e.clientY;

? ? drawRect(x,y)

})

canvas.addEventListener('click',(e)=> {

? ? isMove = !isMove;

});

```

isMove變量就是個開關(guān),我們總不能一直移動吧,那也太累了,鼠標(biāo)點擊的就暫停。 對于上文為什么要x - halfwidth, y - halfheight

這里和大家解釋下: strokeRect 是從矩形的左上角開始畫的,但是呢我想把矩形放在鼠標(biāo)中心位置, 所以做一個寬度和高度相減就可以完美展示了。

# 點的移動

首先第一個問題就是? 我們怎么知道我們選擇的是哪一個點呢,這里我做了一個簡單的判斷就是通過判斷鼠標(biāo)點擊的位置和矩形的每個點的位置作比較,看看哪一個離得近就作為目標(biāo)點。因為我們2d其實點的移動其實也就是找這個點關(guān)聯(lián)的線段, 所以我們只需要重新生成關(guān)聯(lián)的線段就好了,但是這里有一個比較難以處理的地方? 就是移動一個半圓? 如果我們移動半圓的斷點, 這里就涉及到圓弧的改變了,可能變成橢圓弧或者說用二階、三階貝塞爾曲線去表達(dá)。還有就是移動一個圖形和畫布上其他圖形形成了切割? 是不是也要切割算法?其實可以用**Clipper**去求交并差,感興趣的同學(xué)可以自行去了解一下,但是這些不在本篇文章所想要闡述中。 本篇文章所有的例子(只支持直線也就是LineSegment)。

OK,我們第一步我們得去重新表達(dá)矩形,因為他不夠通用準(zhǔn)確的是重新表達(dá)四邊形, 矩形和正方形只是其中的特列。這里我給出原因? 為什么呢一個很簡答的case,移動四邊形的一個點,他可能變成下面這樣:

![Xnip2021-06-22_23-02-28.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/24274f78ecc54f22b22e648fca62692f~tplv-k3u1fbpfcp-watermark.image)

ok 為了下面好表達(dá)我們新建一個Point2d這個類, 將畫布上的每一個點都用一個實例去表示。

```js

class Point2d {

? ? constructor(x,y) {

? ? ? ? this.x = x || 0;

? ? ? ? this.y = y || 0;

? ? }

? ? clone() {

? ? ? ? return this.constructor(this.x, this.y);

? ? }

? ? add(v) {

? ? ? ? this.x += v.x;

? ? ? ? this.y += v.y

? ? ? ? return this;

? ? }

? random() {

? ? ? ? this.x = Math.random() *1800;

? ? ? ? this.y = Math.random() * 800;

? ? ? ? return this

? ? }

}

```

接下來我們就隨便在畫布上一鼠標(biāo)的位置分別加上矩形的長度和寬度,畫出矩形。 代碼如下:

```js

function drawFourPolygon(x, y ,width = 50, height = 50) {

? ? ctx.clearRect( 0, 0, 1800, 800 );

? ? ctx.beginPath();

? ? ctx.moveTo(x- width /2, y - height/2)

? ? ctx.lineTo(x+ width / 2, y -height/2 )

? ? ctx.lineTo(x+ width / 2, y + height/2 )

? ? ctx.lineTo(x - width / 2, y + height/2 )

? ? ctx.closePath()

? ? ctx.stroke()

}

```

為了交互更加完美, 鼠標(biāo)第一次點擊確定移動的開始點, 然后 鼠標(biāo)不停地移動就是移動的終止點, 這樣就確定了一個向量。 這里為了移動的時候更加明顯我增加了虛線功能,代碼如下

```js

function drawDashLine(start, end) {

? ? ? if (!start || !end) {

? ? ? ? ? return

? ? ? }

? ? ? ctx.strokeStyle = 'red';

? ? ? ctx.setLineDash( [5, 10] );

? ? ? ctx.beginPath();?

? ? ? ctx.moveTo( start.x, start.y );

? ? ? ctx.lineTo( end.x, end.y );

? ? ? ctx.closePath()

? ? ? ctx.stroke();

? }

```

這里用到的就是canvas setLineDash 這個api 參數(shù)的含義,實線的距離5、空白的距離10 如此往復(fù)的走下去形成虛線。start和end 就是鼠標(biāo)點擊確定的就是start 點, 然后鼠標(biāo)不停的移動就是end點。 這里有一個小提醒就是我鼠標(biāo)移動的過程中先畫了虛線,然后又畫了矩形所以呢? 矩形我們還是實線。我們這里對畫矩形代碼做了修改,還是把虛線還原過來。

代碼如下:

```js

ctx.setLineDash([]);

```

OK整體的交互出來了,我先給大家看下效果:

![Jun-23-2021 22-27-27](/Users/wangzhengfei/Desktop/Jun-23-2021 22-27-27.gif)

是不是有點感覺了哈哈哈?從畫面上看這還是整體的移動不是點的移動, 由于我畫的圖形以鼠標(biāo)點擊的那個點去畫矩形的,我的下一篇文章會給大家介紹不規(guī)則多邊形點的移動,本篇文章我們還是假設(shè)我移動的是**右上角的那個點**。OK**我們由移動的開始點和結(jié)束點, 可以得到一個移動的向量, 所以我們只要將要移動的點 和這個向量相加。這樣我們是不是實現(xiàn)了點的移動。**

```js

const moveVec = end.clone().sub(start);

const rightTop = new Point2d(x+ width / 2, y - height/2).clone().add(moveVec)

```

這里我改變了右上角的點,但是呢有一個問題就是我們點擊也是走的同一個函數(shù),所以我們得加個開關(guān)去判斷下,主要是用來判讀是第一次點擊還是移動就好了代碼如下:

```js

ctx.lineTo(isSelect ? rightTop.x : x+ width / 2, isSelect ? rightTop.y : y height/2)

// 看下click和move 事件 開關(guān)就是isSelect這個變量

canvas.addEventListener('mousemove',(e)=>{

? ? if(!isMove) {

? ? ? ? return

? ? }

? ? const x = e.clientX;

? ? const y = e.clientY;

? ? clearRect();

? ? end = new Point2d(x,y);

? ? drawDashLine(start,end);

? ? drawFourPolygon(start)

})

canvas.addEventListener('click',(e)=> {

? ? // 這是一個每次清除畫布的函數(shù)

? ? clearRect()

? ? isMove = !isMove;

? ? const x = e.clientX;

? ? const y = e.clientY;

? ? start? = new Point2d(x,y);

? ? drawFourPolygon(start)

? ? isSelect = true;

});

```

效果圖如下:

![Jun-23-2021 23-00-57.gif](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/92def62592954a09a2adc49560a7af4f~tplv-k3u1fbpfcp-watermark.image)

哈哈哈是不是十分的絲滑和流暢, 發(fā)現(xiàn)canvas 的畫圖的性能還是非常不錯的。但是還是有一個問題就是,確定結(jié)果,看上面代碼我們確定結(jié)果是有問題的。 所以我以按住alt鍵結(jié)束為確定結(jié)果這就十分完美了,代碼就不在這里展現(xiàn)了。

# 線的移動

有了點的移動,線的移動就顯示的十分簡單。 線的移動其實就是對應(yīng)的點的移動。 我們以右邊這條線為例子: 代碼改寫如下:

```js

function drawFourPolygon( point, width = 50, height = 50) {

? ? if(!point) {

? ? ? ? return

? ? }

? ? ctx.strokeStyle = 'black'

? ? ctx.setLineDash([]);

? ? ctx.beginPath();

? ? const { x, y } = point;

? ? const moveVec = end.clone().sub(start);

? ? // 其實就是 右上和右下這兩個點同時移動

? ? const rightTop = new Point2d(x+ width / 2, y - height/2).clone().add(moveVec)

? ? const rightBottom = new Point2d(x+ width / 2, y + height/2).clone().add(moveVec)

? ? ctx.moveTo(x- width /2, y - height/2)

? ? ctx.lineTo(isSelect ? rightTop.x : x+ width / 2, isSelect ? rightTop.y : y - height/2)

? ? ctx.lineTo(isSelect ? rightBottom.x : x+ width / 2, isSelect ? rightBottom.y : y + height/2)

? ? ctx.lineTo(x - width / 2, y + height/2 )

? ? ctx.closePath()

? ? ctx.stroke()

? }

```

我們看下效果圖:

![Jun-23-2021 23-13-20.gif](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8b4940cb22f64add964fba1b22d0850d~tplv-k3u1fbpfcp-watermark.image)

# 總結(jié)

本篇文章主要介紹的2d圖形下最基本的變化**移動**,無論是線的移動還是面的移動最終都是點的移動。其實移動除了用向量表示還可以用矩陣, 或者說我們旋轉(zhuǎn)移動縮放等等命令都可以用矩陣變化表示。 最后還是感謝大家看到最后,碼字不容易,如果看了對你有幫助的, 歡迎點個贊??和關(guān)注。 你的支持是我持續(xù)更新好文章的最大動力。 所有代碼都在我的[github](https://github.com/wzf1997/blog)上。歡迎大家star。

公眾號: 前端圖形? 持續(xù)分享前端可視化知識

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容