趁著清明放假的空閑,將之前寫過的代碼整理了一下,發現了一個比較有意思的項目,該項目其實也比較簡單,就是利用Canvas的各種原生API在圖像中繪制一些基礎圖形,以及一些圖形的更改操作。順便借此項目復習一下Canvas基礎。
目前實現功能
基本實現功能
- 圖片的放大縮小和拖拽
- 繪制多邊形并修改
- 繪制矩形并修改
- 繪制線段(暫無修改)
- 繪制箭頭(暫無修改)
圖片的放大和縮小
- drawImage() 圖片的繪制
// 繪制圖片
drawImage = () => {
if (this.$imageDom) {
try {
this._context.drawImage(
this.$imageDom, // 圖片元素
0, // 開始剪切的 x 坐標位置
0, // 開始剪切的 y 坐標位置
this.imageOriginWidth, //被剪切圖像的寬度
this.imageOriginHeight, //被剪切圖像的寬度
this.offsetX, // 在畫布上放置圖像的 x 坐標位置
this.offsetY, //在畫布上放置圖像的 y 坐標位置
this.imageOriginWidth * this.currentRatio, //要使用的圖像的寬度
this.imageOriginHeight * this.currentRatio //要使用的圖像的高度
);
return Promise.resolve();
} catch (e) {
console.log(e)
}
}
}
- 計算畫布上放置圖像的坐標位置
按照上圖方式去計算要放置的圖像的點坐標
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()
根據上述原生API繪制線,多邊形的繪制即為坐標點大于2的路徑的閉合曲線。
判斷點是否在多邊形內
- isPointInPath
繪制當前閉合路徑,根據該函數判斷點是否在路徑內。
判斷點是否在線上
由于上述方法是判斷點是否在路徑內,就無法判斷點是否在線上了,我采用的方法如下:
- 先判斷點的坐標是否在線的坐標范圍內,如果不在則點不在線上
- 如果1滿足,則根據直線公式 y = kx + b 通過線段已知兩點坐標求出斜率k和偏移值b;
- 根據線段的斜率和垂直線的斜率 k * k1 = -1,求出垂直線斜率,再根據當前點計算出通過該點的垂直線公式 y = (-1/k)x + m;
- 根據垂直相交線公式求出交點坐標,根據兩點(當前點和交點坐標)求出線段距離,如果該距離小于誤差范圍值,則認為點在線上,反之則認為不在線上。
判斷點是否在線上
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); // 計算斜率
const verSlop = _calVerticalSlop(slop); // 計算垂直斜率
const x1 = p1[0] || 0;
const y1 = p1[1] || 0;
if (slop != 0 && verSlop != 0) {
if (y2 == slop * x2 + y1 - slop * y1) {
// 點在當前直線上
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) {
// 說明是點在線上
return true;
}
return false;
}
繪制矩形
- rect()
根據原生API繪制圖形,修改時的判斷方式同多邊形的判斷。
實現原理就介紹到這里,更多詳細信息請去https://github.com/jdkwky/my-vue-example/tree/master/src/view/canvas中了解~