60分前端之canvas

前言

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

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

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

话不多说,直接开干。

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

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

微信小程序类目的坑

微信小程序文档

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

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

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

问题来了!

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

而且还改不了!!

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

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

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

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

数独81宫格实现过程

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

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

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

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;
}

实现效果是这样的:

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

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

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

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

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

加多少呢?试呗。😁

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

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

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

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

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

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

css 复制代码
.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 的形式。

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

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

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

css 复制代码
.cell:nth-child(9n+1) {
    border-left-width: 1px;
}

现在看起来顺眼了很多。

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

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

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

css 复制代码
/* 设置第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;
}

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

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

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

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

对象模型

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

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

定义对象模型:

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

初始化对象模型:

typescript 复制代码
Page({
  data: {
      sudoku: <SudokuItem[]>[
          {numerical: 0, tower: false, floor: false},
          {numerical: 0, tower: false, floor: false},
          {numerical: 0, tower: true, floor: false},
          ...
      ]
  }
})

实际初始化的时候,通过JS代码初始化sudoku,并计算哪个元素的towertrue,哪个floortrue

页面渲染的时候:

html 复制代码
 <view class="sudoku">
    <view wx:for="{{mission}}" wx:for-index="idx" wx:key="idx" wx:for-item="item"
      class="cell-item {{item.tower ? 'tower' : ''}} {{item.floor ? 'floor' : ''}}"
      bindtap="focusTheCell"
      data-index="{{idx}}"
    >
      {{item.numerical == 0 ? undefined : item.numerical}}
    </view>
  </view>
css 复制代码
.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画布。

html 复制代码
<canvas id="sudoku" width="460" height="460"></canvas>
javascript 复制代码
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)为坐标,页面坐标这个东西,相信很多人应该都已经知道了。

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

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

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

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

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

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

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

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

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

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

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

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

javascript 复制代码
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();
}

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

ctx.lineWidth:设置线段的宽度

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

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

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

下面是完整的代码:

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Canvas 练习</title>
</head>
<body>
    <canvas id="sudoku" width="460px" height="460px"></canvas>
    <script type="text/javascript">
        const canvas = document.getElementById("sudoku");
        const ctx = canvas.getContext("2d");
        // 单元格的宽高
        const cellSize = 50;

        for (let i = 0; i < 10; i++) {
            // 设置画笔宽度
            ctx.lineWidth = i % 3 == 0 ? 3 : 1;

            ctx.beginPath();
            // 画横线,留5px的间距
            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();
        }
    </script>
</body>
</html>

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

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

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

最后

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

相关推荐
苹果酱056716 分钟前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱36 分钟前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
天下无贼!1 小时前
2024年最新版Vue3学习笔记
前端·vue.js·笔记·学习·vue
Jiaberrr1 小时前
JS实现树形结构数据中特定节点及其子节点显示属性设置的技巧(可用于树形节点过滤筛选)
前端·javascript·tree·树形·过滤筛选
赵啸林1 小时前
npm发布插件超级简单版
前端·npm·node.js
罔闻_spider2 小时前
爬虫----webpack
前端·爬虫·webpack
吱吱鼠叔2 小时前
MATLAB数据文件读写:1.格式化读写文件
前端·数据库·matlab
爱喝水的小鼠2 小时前
Vue3(一) Vite创建Vue3工程,选项式API与组合式API;setup的使用;Vue中的响应式ref,reactive
前端·javascript·vue.js
盏灯2 小时前
前端开发,场景题:讲一下如何实现 ✍电子签名、🎨你画我猜?
前端
WeiShuai2 小时前
vue-cli3使用DllPlugin优化webpack打包性能
前端·javascript