Canvas 应用之掘金 logo
多边形
moveTo,lineTo 方法的应用
通过前面的学习我们已经知道,moveTo,lineTo 方法可以用来画三角形,正方形,自然而然多边形也是用这两个方法来绘制。
绘制一个多边形箭头
<template>
<div ref="mainContent" class="polygon">
<canvas ref="cnv" width="200" height="150" style="border: 1px dashed gray"></canvas>
</div>
</template>
<script setup>
import {ref, onMounted} from "vue";
const cnv = ref();
const drawPolygon = (ctx) => {
// 确定顶点
ctx.moveTo(100, 20);
ctx.lineTo(150, 50);
ctx.lineTo(120, 50);
ctx.lineTo(120, 130);
ctx.lineTo(80, 130);
ctx.lineTo(80, 50);
ctx.lineTo(50, 50);
ctx.lineTo(100, 20);
ctx.stroke();
}
onMounted(() => {
const ctx = cnv.value.getContext('2d');
drawPolygon(ctx);
});
</script>
这样我们就画出了一个向上的箭头,但是,是否在上述的代码中发现,lineTo 这个方法是否一直在重复的执行,对于一个程序员来说,这估计内心难以容忍,选择将 drawPolygon 方法进行封装一下。
const drawPolygon = (ctx) => {
const points = [ 100, 20, 150, 50, 120, 50, 120, 130, 80, 130, 80, 50, 50, 50 ];
points.forEach((point, index) => {
if (index && index % 2 === 1) {
if (index === 1) {
ctx.moveTo(points[index - 1], point);
} else {
ctx.lineTo(points[index - 1], point);
}
}
});
// 形成一个多边形的必要条件就是起点与终点相同,也即是首尾相连
ctx.lineTo(points.at(0), points.at(1));
ctx.stroke();
}
这样封装一下是否感觉要好多了,但是 forEach 里边的计算函数的判断还是太复杂了,看来需要介绍新的方法了,ctx.beginPath(),ctx.closePath()。
- ctx.beginPath():用于开始一条新路径
- ctx.closePath():用于关闭路径
我们再次来封装一下绘制一个多边形,这次附上一套完整代码来看一看。
<template>
<div ref="mainContent" class="polygon">
<canvas ref="cnv" width="200" height="150" style="border: 1px dashed gray"></canvas>
</div>
</template>
<script setup>
import {ref, onMounted} from "vue";
const cnv = ref();
/**
* 根据顶点列表绘制多边形
* @param ctx
* @param points
*/
const drawPolygon = (ctx, points) => {
try {
if (!ctx) {
throw new Error('未获取到上下文');
}
if (points.length % 2 === 1) {
throw new Error('顶点列表不符合规范');
}
ctx.beginPath();
points.forEach((point, index) => {
if (index && index % 2 === 1) {
ctx.lineTo(points[index - 1], point);
}
});
ctx.closePath();
ctx.stroke();
} catch (error) {
console.error(error);
}
}
onMounted(() => {
const ctx = cnv.value.getContext('2d');
const points = [ 100, 20, 150, 50, 120, 50, 120, 130, 80, 130, 80, 50, 50, 50 ];
drawPolygon(ctx, points);
});
</script>
对于多边形的绘制,归根结底就是一个寻找点坐标的问题,只要把点找对,找齐,那么就可以画出多边形来。
绘制一个掘金的 Logo
根据现有的直线这一章的学习,我们可以绘制出基础的多边形,那我们来看一看怎么来绘制一个掘金的 Logo。
因为不知道掘金 Logo 的尺寸及比例,所以只能用最笨的办法,就是把图下载下来,进行粗糙的测量,肯定会有误差,但是不影响,只是做一个练习,测量用到的软件 《Geogebra》。
我们来看看在 Geogebra 中画的的测量图:
由于,这个软件是数学几何软件,在绘制的时候,尝试了转换为 W3C 坐标系,但是失败了(如果有知道比较好的图形绘制辅助软件,还请 jym 评论告知一下),但是这并不影响我们进行测量,测量数据都不是精确值,难免有些误差,还请见谅。
<template>
<canvas ref="cnv" width="200" height="150" style="border: 1px dashed gray"></canvas>
</template>
<script setup>
import {ref, onMounted} from "vue";
const cnv = ref();
function getPoints(ANGLE, pointGroups, basePoint) {
const [baseX, baseY] = basePoint;
return pointGroups.map(group => {
return group.map(points => {
let pointX;
if (points.onLine === 'middle') {
pointX = baseX;
} else {
const width = points.height * Math.tan(ANGLE);
pointX = points.onLine === 'left' ? baseX - width : baseX + width;
}
const pointY = baseY + points.height;
return [pointX, pointY];
})
})
}
function drawLogo(ctx, ANGLE, pointGroups, basePoint) {
const shouldDrawPointGroups = getPoints(ANGLE, pointGroups, basePoint);
shouldDrawPointGroups.forEach(group => {
group.forEach((point, index) => {
if (index === 0) {
ctx.moveTo(point[0], point[1]);
} else {
ctx.lineTo(point[0], point[1]);
}
});
});
ctx.fillStyle = "#1f80ff";
ctx.fill();
}
onMounted(() => {
const ctx = cnv.value.getContext('2d');
const ANGLE = Math.PI / 180 * (90 - 39);
const basePoint = [100, 10];
const pointGroups = [
// 第一组顶点关系
[
{ height: 0, onLine: 'middle' },
{ height: 15, onLine: 'left'},
{ height: 30, onLine: 'middle' },
{ height: 15, onLine: 'right'},
{ height: 0, onLine: 'middle' }
],
// 第二组顶点关系
[
{ height: 59, onLine: 'middle' },
{ height: 30, onLine: 'left'},
{ height: 40, onLine: 'left'},
{ height: 80, onLine: 'middle' },
{ height: 40, onLine: 'right'},
{ height: 30, onLine: 'right'},
{ height: 59, onLine: 'middle' }
],
// 第三组顶点关系
[
{ height: 106, onLine: 'middle' },
{ height: 54, onLine: 'left'},
{ height: 65, onLine: 'left'},
{ height: 128, onLine: 'middle' },
{ height: 65, onLine: 'right'},
{ height: 54, onLine: 'right'},
{ height: 106, onLine: 'middle' }
]
];
drawLogo(ctx, ANGLE, pointGroups, basePoint);
});
</script>
我们先来看看画出来的 logo 效果:
因为坐标系的缘故,所以在绘制时,需要自行把视图倒过来看,为了将视图安排的尽量美观,所有不是在x轴上开始画图,而是以顶点 E(100, 10)开始向下绘制,如图,已知角度 ∠DEx 为 39°,那么 ∠DER 即为 90° - 39° = 51°,已知线段 ER = 15px,那么线段 DR 的长度就能通过三角函数求出,在求解三角函数时,需要把角度制转为弧度制,再进行计算,我们用同样的方式,可以测出线段 AE、SE、TE、HE、UE、KE、ME、PE 的长度,可以通过三角函数直接求出对应的点的x坐标,或者利用相似三角形等比属性计算也是可以的。这个思路总结一下就是,通过辅助线把所有点的纵坐标测出来,利用三角函数将对应点的横坐标算出来,得到所有点的坐标,最后再进行绘制。绘制时,一定要记住绘制的先后顺序,可以选择顺时针,也可以逆时针方向,但得一直保持一个方向。
我们在学习的时候,想着法的让自己能把学到的东西及时的进行练习,把学到的知识点巩固起来,jym们一起加油吧!