趁著清明放假的空閑,將之前寫(xiě)過(guò)的代碼整理了一下,發(fā)現(xiàn)了一個(gè)比較有意思的項(xiàng)目,該項(xiàng)目其實(shí)也比較簡(jiǎn)單,就是利用Canvas的各種原生API在圖像中繪制一些基礎(chǔ)圖形,以及一些圖形的更改操作。順便借此項(xiàng)目復(fù)習(xí)一下Canvas基礎(chǔ)。

目前實(shí)現(xiàn)功能
基本實(shí)現(xiàn)功能
- 圖片的放大縮小和拖拽
- 繪制多邊形并修改
- 繪制矩形并修改
- 繪制線段(暫無(wú)修改)
- 繪制箭頭(暫無(wú)修改)
圖片的放大和縮小
- drawImage() 圖片的繪制
// 繪制圖片
drawImage = () => {
if (this.$imageDom) {
try {
this._context.drawImage(
this.$imageDom, // 圖片元素
0, // 開(kāi)始剪切的 x 坐標(biāo)位置
0, // 開(kāi)始剪切的 y 坐標(biāo)位置
this.imageOriginWidth, //被剪切圖像的寬度
this.imageOriginHeight, //被剪切圖像的寬度
this.offsetX, // 在畫(huà)布上放置圖像的 x 坐標(biāo)位置
this.offsetY, //在畫(huà)布上放置圖像的 y 坐標(biāo)位置
this.imageOriginWidth * this.currentRatio, //要使用的圖像的寬度
this.imageOriginHeight * this.currentRatio //要使用的圖像的高度
);
return Promise.resolve();
} catch (e) {
console.log(e)
}
}
}
- 計(jì)算畫(huà)布上放置圖像的坐標(biāo)位置

按照上圖方式去計(jì)算要放置的圖像的點(diǎn)坐標(biāo)
getOffset = (pointX, pointY, scale, ratio, dir) => {
if (pointX && pointY) {
// 獲取圖片
const width = this.imageOriginWidth * (scale - ratio * dir);
const height = this.imageOriginHeight * (scale - ratio * dir);
const x = this.offsetX;
const y = this.offsetY;
if ((pointX < x) && (pointY >= y && pointY <= y + height)
) {
// 1
this.offsetY = pointY - (pointY - this.offsetY) / (scale - ratio * dir) * scale;
} else if ((pointX < x) && pointY >= y + height) {
// 2
this.offsetX = x;
this.offsetY = (ratio * dir * this.imageOriginHeight - this.offsetY) * (-1);
} else if (pointX > x + width && (pointY >= y && pointY <= y + height)) {
// 5
this.offsetY = pointY - (pointY - this.offsetY) / (scale - ratio * dir) * scale;
this.offsetX = (ratio * dir * this.imageOriginWidth - this.offsetX) * (-1);
} else if ((pointX >= x && pointX <= x + width) && (pointY > y + height)) {
// 3
this.offsetX = pointX - (pointX - this.offsetX) / (scale - ratio * dir) * scale;
this.offsetY = (ratio * dir * this.imageOriginHeight - this.offsetY) * (-1);
} else if (pointY < y && (pointX >= x && pointX <= x + width)) {
// 7
this.offsetX = pointX - (pointX - this.offsetX) / (scale - ratio * dir) * scale;
this.offsetY = y;
} else if (pointX > x + width && (pointY > y + height)) {
// 4
this.offsetX = (ratio * dir * this.imageOriginWidth - this.offsetX) * (-1);
this.offsetY = (ratio * dir * this.imageOriginHeight - this.offsetY) * (-1);
} else if (pointX > x + width && pointY < y) {
// 6
this.offsetY = y;
this.offsetX = (ratio * dir * this.imageOriginWidth - this.offsetX) * (-1);
} else if (pointX < x && pointY < y) {
// 8
this.offsetX = x;
this.offsetY = y;
} else {
// 9
this.offsetX = pointX - (pointX - this.offsetX) / (scale - ratio * dir) * scale;
this.offsetY = pointY - (pointY - this.offsetY) / (scale - ratio * dir) * scale;
}
}
}
多邊形的繪制(線的繪制、箭頭的繪制)
- moveTo()
- lineTo()
- closePath()
根據(jù)上述原生API繪制線,多邊形的繪制即為坐標(biāo)點(diǎn)大于2的路徑的閉合曲線。
判斷點(diǎn)是否在多邊形內(nèi)
- isPointInPath
繪制當(dāng)前閉合路徑,根據(jù)該函數(shù)判斷點(diǎn)是否在路徑內(nèi)。
判斷點(diǎn)是否在線上
由于上述方法是判斷點(diǎn)是否在路徑內(nèi),就無(wú)法判斷點(diǎn)是否在線上了,我采用的方法如下:
- 先判斷點(diǎn)的坐標(biāo)是否在線的坐標(biāo)范圍內(nèi),如果不在則點(diǎn)不在線上
- 如果1滿足,則根據(jù)直線公式 y = kx + b 通過(guò)線段已知兩點(diǎn)坐標(biāo)求出斜率k和偏移值b;
- 根據(jù)線段的斜率和垂直線的斜率 k * k1 = -1,求出垂直線斜率,再根據(jù)當(dāng)前點(diǎn)計(jì)算出通過(guò)該點(diǎn)的垂直線公式 y = (-1/k)x + m;
- 根據(jù)垂直相交線公式求出交點(diǎn)坐標(biāo),根據(jù)兩點(diǎn)(當(dāng)前點(diǎn)和交點(diǎn)坐標(biāo))求出線段距離,如果該距離小于誤差范圍值,則認(rèn)為點(diǎn)在線上,反之則認(rèn)為不在線上。

判斷點(diǎn)是否在線上
function isPointInLinePath(line, dot, threshold) {
const x2 = dot[0] ? dot[0] : 0;
const y2 = dot[1] ? dot[1] : 0;
const p1 = line[0];
const p2 = line[1];
const [p1X, p1Y] = p1;
const [p2X, p2Y] = p2;
let l = threshold + 1;
let x = 0;
let y = 0;
if (
((p1X <= x2 && x2 <= p2X) || (p2X <= x2 && x2 <= p1X)) &&
((p1Y <= y2 && y2 <= p2Y) || (p2Y <= y2 && y2 <= p1Y))
) {
const slop = _calSlop(p1, p2); // 計(jì)算斜率
const verSlop = _calVerticalSlop(slop); // 計(jì)算垂直斜率
const x1 = p1[0] || 0;
const y1 = p1[1] || 0;
if (slop != 0 && verSlop != 0) {
if (y2 == slop * x2 + y1 - slop * y1) {
// 點(diǎn)在當(dāng)前直線上
x = x2;
y = y2;
} else {
x = parseFloat(
(y2 - y1 + slop * x1 - verSlop * x2) / (slop - verSlop)
);
y = parseFloat(slop * x + y1 - slop * x1);
}
} else {
// 垂直于x軸或平行于x軸
if (x1 == p2X) {
// 平行于y軸
x = x1;
y = y2;
} else if (y1 == p2Y) {
// 平行于x軸
x = x2;
y = y1;
}
}
if (
(p1X <= x && x <= p2X) ||
(p2X <= x && x <= p1X && (p1Y <= y && y <= p2Y)) ||
(p2Y <= y && y <= p1Y)
) {
l = parseInt(Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2)));
}
}
if (l < threshold) {
// 說(shuō)明是點(diǎn)在線上
return true;
}
return false;
}
繪制矩形
- rect()
根據(jù)原生API繪制圖形,修改時(shí)的判斷方式同多邊形的判斷。
實(shí)現(xiàn)原理就介紹到這里,更多詳細(xì)信息請(qǐng)去https://github.com/jdkwky/my-vue-example/tree/master/src/view/canvas中了解~