# 前言
在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,移動四邊形的一個點,他可能變成下面這樣:

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整體的交互出來了,我先給大家看下效果:

是不是有點感覺了哈哈哈?從畫面上看這還是整體的移動不是點的移動, 由于我畫的圖形以鼠標(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;
});
```
效果圖如下:

哈哈哈是不是十分的絲滑和流暢, 發(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()
? }
```
我們看下效果圖:

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