趁着清明放假的空闲, 将之前写过的代码整理了一下, 发现了一个比较有意思的项目, 该项目其实也比较简单, 就是利用 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 中了解~
来源: http://www.jianshu.com/p/96a467e96e60