注册
web

60分前端之canvas

前言


最近在等前端对接,没事干就折腾一下前端。写了这么多年服务端,一直都是写api接口,还没好好学过前端呢。前端框架那么多,什么Vue、React,还分移动端、PC端,还有什么响应式、自适应,各种花里胡哨的东西,也没那么多精力去折腾,直接上手小程序吧。


小程序开发虽然简单,但基本的CSS样式还是得学一下吧:

1. MDN官方CSS教程
2. 谷歌出的CSS教程
3. 谷歌HTML教程


在国内,微信小程序开发,应该是每个前端程序员必会的技术了吧。


话不多说,直接开干。


写了这么多年的hello world了,学一门“新”的技术还是很简单的。而且咱要求也不高,毕竟不是专门写前端的,所以对于前端的技术,只要做到60分就够了。


所以,下面很多东西,我们都是能忽略就忽略,主打一个囫囵吞枣、不求甚解,以解决问题为主。


微信小程序类目的坑


微信小程序文档


学习任何技术,官方文档都是最好的入门方式之一。


查看微信小程序官方文档,照着文档一步一步往下走,有个两年开发经验的人,基本都能搞定,没什么难度。


但是问题来了,在配置小程序时,需要设置小程序类目。这里提前说一下,因为是练手的项目,所以我写的这个小程序是个大杂烩,既有数独小游戏,又有背单词。由于微信小程序类目可以选择多个,所以我先选择了小游戏......


问题来了!


微信小程序类目,选择了游戏之后,就不能选择其他的了!


而且还改不了!!


改不了就算了,更坑的是,小游戏的代码结构和小程序还不一样,没办法通用。选择小游戏,就必须用小游戏的代码结构,没办法和小程序混用。


练手的几个小游戏,我都是用小程序实现的,没办法在小游戏模式下运行。


网上搜了一下,发现很多人都遇到过这样的问题,也都向官方反映过了,但官方都没有给回复。顺带一提,好像社区里很多问题,官方基本都不回复的。


没办法,只能重新注册一个号了。或者注销之前的号,然后重新注册也行。嫌麻烦,干脆重新注册一个号吧。


数独81宫格实现过程

数独游戏估计很多人都只听过,但没实际玩过,这里就不详细介绍数独游戏了,我们只需要知道一点,数独游戏需要有一个81宫格。

0b274904fd0bd2770b243067a3f503a0.jpg

可以看到,数独的81宫格最外层边框加粗,里面每三列的右边框、每三行的下边框也加粗,相当于是9个9宫格合并在一起。

作为前端的初学者,想要实现一个样式,第一反应肯定是用CSS来实现。

CSS选择器

先来把基本的框给实现了。

.sudoku {
box-sizing: border-box;
width: 700rpx;
min-height: 730rpx;
display: grid;
grid-template-columns: repeat(9, 1fr);
grid-template-rows: repeat(9, 1fr);
gap: 0;
border: 2px solid #000000;
}
.cell {
background-color: #FFFFFF;
border: 1px solid #000000;
border-right-width: 0;
border-bottom-width: 0;
}

实现效果是这样的:

12bc143c479f349d353d213ffc71a063.jpg

不知道大家有没有注意到这段样式min-height: 730rpx;,设置最小高度。


一开始设置height: 700rpx;,宽高都是700rpx,在iPhone12/13 Pro Max机型下没问题,可是在iPhone12/13 Pro机型下,最下面一行会少一截,高度不够了!


前端的同学都知道CSS的一个概念,叫盒模型。这里高度不够是因为每个小格子的边框也有个宽度,实际总高度应该是每个格子的高度加上格子边框的宽度之和。宽度也是一样的。


这里有一个问题,就算宽高要加上每个小格子边框的宽度,最终的宽高也应该一样的才对啊?!为什么高度反而比宽度还大了呢?!


不理解!算了,毕竟是写服务端的,前端只要做到60分就够了。解决问题就行。高度不够的话,给它加高不就行了。/doge


加多少呢?试呗。😁


好了,在iPhone12/13 Pro机型下问题是解决了,但其他机型下呢?


这个好办,既然知道高度可能不够,继续加高的话,怕影响其他机型下的效果,那干脆给一个最低高度,超过这个高度就让它自己加高,简单粗暴。


它已经是个成熟的高度了,已经学会了自己长高。


还有一种更直接的办法,就是不设置高度,让它自己撑开。但是这样有一个不好的点,高度在撑开的过程中,会很明显看到页面抖动。设置一个最低高度的话,抖动就不明显了。


只要我看不到它抖动,它就不存在。

f1d977b34a08adfd7ff1d1696c64823a.jpg

基本的形状已经有了,下面开始对内部指定的边框进行加粗。先来对每三列的右边框加粗,看过上面的CSS教程的话,稍微学点CSS知识的人都知道,CSS有个伪类选择器,我们用选择器nth-child来实现:

.cell:nth-child(3n+1) {
border-left-width: 2px;
}

:nth-child(n) 选择器匹配父元素中的第 n 个子元素,元素类型没有限制。


n 可以是一个数字,一个关键字,或者一个公式。



这里,我们用的是公式的方式。



使用公式(an+ b)a代表一个循环的大小,n是一个计数器(从0开始),以及b是偏移量。ab都必须是整数,an 必须写在 b 的前面,不能写成 b+an 的形式。

4e95904e088d6b12bee19dddc13f9bdd.jpg

第一列左边框看起来太粗了,虽然样式上看起来边框的宽度是2px,但视觉上却像是4px也不知道是什么原因?


不管它什么原因了,毕竟我们不是专业的前端,就像上面说的那样,我们只需要做到60分就够了。


既然视觉上看起来像是4px,那就让它在视觉上看起来像2px

.cell:nth-child(9n+1) {
border-left-width: 1px;
}

b99c6dc41a3152618f44483a0aac2b43.jpg

现在看起来顺眼了很多。


宽度明明设置的是1px,为什么视觉上看起来像2px,不知道什么原因?老规矩,不管!😁


每三列的左边框加粗,这个已经实现了,下面开始实现每三行的下边框加粗


可是问题来了,使用:nth-child选择器貌似不能选中连续的子元素,网上搜了一堆,都没有解决方案,后来问了一下ChatGPT,给的回复是这样的:

/* 设置第3行和第6行的下边框宽度为2px */
.cell:nth-child(3n+1):nth-child(n+7),
.cell:nth-child(3n+2):nth-child(n+8),
.cell:nth-child(3n+3):nth-child(n+9) {
border-bottom-width: 2px;
}

还亲切地给了注释。可惜根本用不了。

3eeae3b7d6f0346cff80168556f49e0d.jpg

除了第一行前6个的下边框没加粗外,其他元素的下边框全加粗了!


题外话:ChatGPT对CSS的问题,一本正经地胡说八道,给的答案基本都用不了。


折腾了好久,都没折腾出怎么用CSS选择器实现每三行下边框加粗这个效果,算了,换种方式吧。


对象模型


数独游戏不但要有这个宫格,相应的宫格里还得要有数字的。最终的效果应该像这样:

f987119b3a309893be46985d958ad83e.jpg

既然没办法自动计算出每三列和每三行的元素,那我们就用笨办法,手动给它们全标记出来。

定义对象模型:

interface SudokuItem {
numerical: number, // 数值
tower: boolean, // 左边框是否需要加粗
floor: boolean // 下边框是否需要加粗
}

初始化对象模型:

Page({
data: {
sudoku: [
{numerical: 0, tower: false, floor: false},
{numerical: 0, tower: false, floor: false},
{numerical: 0, tower: true, floor: false},
...
]
}
})[]>

实际初始化的时候,通过JS代码初始化sudoku,并计算哪个元素的tower为true,哪个floor为true。

页面渲染的时候:


class="cell-item {{item.tower ? 'tower' : ''}} {{item.floor ? 'floor' : ''}}"
bindtap="focusTheCell"
data-index="{{idx}}"
>
{{item.numerical == 0 ? undefined : item.numerical}}

.sudoku .floor {
border-top-width: 2px;
}

.sudoku .tower {
border-right-width: 2px;
}

好了,基本的样式是实现了,虽然过程很简单粗暴,但咱毕竟是写服务端的,对前端的要求也不高,能做到60分就够了。


继续折腾,上面是用CSS样式实现的81宫格,还有其他办法吗?


必须有啊,前端的Canvas技术也很火啊,这个必须得折腾一下啊。


而且貌似还有很多前端不会Canvas的哦,等我们学会了,就去找前端嘚瑟一下。


Canvas


MDN官方教程


先看Canvas官方教程,嗯......,东西挺多的,好像有点复杂,几十个API,看得人眼花。没关系,毕竟我们是带着问题来学习的。这里,我们只需要几个API就行了。


什么问题?使用Canvas画出一个数独的81宫格。


我在学习一个新技术的时候,都是先上手再深入,不懂的地方暂时先跳过去,前面说过的,主打一个囫囵吞枣、不求甚解。先把问题解决了再说。


我们先暂时抛开小程序,只专注于Canvas。


直接开干。


首页,创建一个Canvas画布。

const canvas = document.getElementById("sudoku");
const ctx = canvas.getContext("2d");

好了,一个画布就创建好了。是不是很简单。而且是固定套路,可以不理解,记住就行了。下次用的时候,直接复制粘贴,再简单改改就行了。


id:和HTML标签的id属性一样。


widthheight:画布的宽高,可以通过CSS属性调整。这里的宽高类似于图片的原始宽高,CSS调整后的宽高就和修改页面图片的宽高一样,实际是对图片进行缩放。


canvas.getContext("2d"):接受一个参数,即上下文的类型。可以传入2dwebglwebgl2bitmaprenderer,用于创建不同类型的上下文。


这里我们传入2d,创建一个二维渲染上下文。


画布创建好了,下面该画画了。


想想看,如果让你在现实中用笔画一个81宫格,你应该怎么做?


首先,得有张白纸,然后提笔,之后开始画。


在Canvas中也一样,白纸我们已经有了,上面创建的画布就是白纸。


下面,该考虑怎么画了。初中数学老师告诉我们,两点确定一条直线,从A点到B点,画一条直线。


Canvas通过路径和填充的方式来绘制图画的,填充我们暂时不管,路径可以简单理解为线段,直线、曲线这些。


每条线段就是一条路径。



生成路径的第一步叫做 beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。



不理解?记住就行了,都是固定套路。先记住怎么用,等bug写多了,自然就理解了。别管代码是不是写的垃圾,先敲出来再说,别只光在脑子里想。


ctx.beginPath():清空子路径列表开始一个新路径。可以简单理解成“提笔”。


ctx.moveTo(x, y):将一个新的子路径的起始点移动到 (x,y) 坐标。可以理解成上面的“从A点......”


ctx.lineTo(x, y):使用直线连接子路径的终点到(x,y)坐标。可以理解成上面的“到B点,画一条直线”


上面的(x, y)为坐标,页面坐标这个东西,相信很多人应该都已经知道了。

8fa76917e26a0d920778800eb7948043.jpg

这里的坐标为画布中的坐标,坐标原点(0, 0)为画布的左上角。

const canvas = document.getElementById("sudoku");
// 创建二维上下文
const ctx = canvas.getContext("2d");
// 从A点
ctx.moveTo(100, 100);
// 到B点
ctx.lineTo(200, 100);

刷新一下页面,什么都没有!


别急,这就和我们现实中画画一样,先要在脑中构思,构思完了,才开始落笔绘画。


同样的道理,上面的那些相当于是在构思,构思完了后,我们得要落笔绘制。


ctx.stroke():根据当前的画线样式,绘制当前或已经存在的路径。


所以,完整的代码应该是这样的:

const canvas = document.getElementById("sudoku");
// 创建二维上下文
const ctx = canvas.getContext("2d");
ctx.beginPath();
// 从A点
ctx.moveTo(100, 100);
// 到B点
ctx.lineTo(200, 100);
// 画线
ctx.stroke();

4a6af25daba13b8ca77238103088378b.jpg

具体可以看一下MDN的示例,使用canvas来绘制图形


好了,一条直线就绘制出来了。


可以了,能画出一条直线,我们可以开始画81宫格了。81宫格就是10条横线、10条竖线组成的。只要计算好每个点的坐标,然后画线就行了。


计算点的坐标,这是数学的范畴,就不过多赘述了,而且点的坐标计算也不难。

const canvas = document.getElementById("sudoku");
const ctx = canvas.getContext("2d");
// 单元格的宽高
const cellSize = 50;

for (let i = 0; i < 10; i++) {
// 画横线,留5px的间距
ctx.beginPath();
ctx.moveTo(5, i * cellSize + 5);
ctx.lineTo(455, i * cellSize + 5);
ctx.stroke();

// 画竖线
ctx.beginPath();
ctx.moveTo(i * cellSize + 5, 5);
ctx.lineTo(i * cellSize + 5, 455);
ctx.stroke();
}

b77a2317840daf1f83c64277c7e53757.jpg

81宫格是画出来了,可还没法用,因为数独中的81宫格,要求最外围边框加粗,里面每三列左边框加粗、每三行下边框加粗。


ctx.lineWidth:设置线段的宽度


还记得上面我们通过CSS选择器给边框加粗时遇到的痛苦吗,现在就简单多了,只需要简单的数学计算就可以了。

// 设置画笔宽度
if (i % 3 == 0) {
ctx.lineWidth = 3;
} else {
ctx.lineWidth = 1;
}

设置完后我们,看一下效果

04013ed14508f630fe8ced4a101c2705.jpg

下面是完整的代码:






<span class="hljs-string">Canvas</span> <span class="hljs-string">练习</span>





之后只要根据小程序的规则,将代码移植到小程序里就行了。至于怎么往数独里填充数字,这个之后再说吧,我们一点一点的来。这里我们只介绍这么画直线。


学一门新技术,一定要多练。数独的81宫格画完了,还可以画点其他的啊。比如画一个象棋的棋盘,或者画一个围棋的棋盘。


多练手,遇到的问题多了,自然就会了。


最后

9f61861a4203eeb1394c5f95a6d8c6a9.jpg

虽然是一个练手的项目,但是既然已经做出来了,干脆就上线算了。

作者:W_懒虫
链接:https://juejin.cn/post/7260820017758093349
来源:稀土掘金

0 个评论

要回复文章请先登录注册