一、前言
最近听了月影老师讲 JS 最基本的三个原则,其中举到的例子 ? 甚是巧妙。有一些是之前做过的 demo,经过深挖,发现竟然有这么深的门道;有一些是之前很少用到的,但切实有用的方法。
月影老师讲课的内容都在 掘金小册 – 前端工程师进阶 10 日谈 [1] 中可以找到。我购买了这本小册,内容十分优质,值得深究。
前端基础三件套各自有其作用:
- HTML 用于搭建网页结构
- CSS 用于规定网页样式
- JS 用于实现网页交互
本文主要记录关于 HTML、CSS、JS 各司其责的开发原则,对月影老师的 “深色浅色模式切换” 例子举一反三,通过实现一个手动切换红绿灯的 demo,体会我日常写的辣鸡代码变优雅的过程。
二、任务目标
路口有一个奇怪的交通信号灯,它只有 红灯 和 绿灯 两种状态。不同于普通的交通信号灯,它只有一盏灯泡,不过这个灯可以显示红绿两种颜色(不能同时显示)。
这个交通信号灯只能通过人工拉闸的方式,切换红绿灯的显示。
规定闸头(“?” 的红色一端向上为 绿灯,向下为 红灯)。
切换红绿灯时,需要同时切换红绿灯正中间显示的文字,以及左下角手闸的方向。
基础的 HTML 结构如下:
<div class="traffic">
<span class="notice">请通行</span>
</div>
<div class="controller">?</div>
复制代码
对应的 SCSS 代码如下:
html,
body {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
}
.traffic {
width: 160px;
height: 160px;
border-radius: 50%;
background-color: green;
display: flex;
justify-content: center;
align-items: center;
.notice {
color: white;
font-size: 2rem;
}
}
.controller {
position: absolute;
bottom: 8rem;
right: 3rem;
font-size: 4rem;
transition: all ease-out 0.4s;
transform-origin: bottom;
cursor: pointer;
}
复制代码
实现效果如下图。
三、大家都能想到的实现方案
我们很快就能想到这样的思路:
- 给手闸绑定点击监听事件;
- 点击手闸时,通过操作 DOM,实现任务目标中的功能。
这种方式比较好实现。
// 获取元素实例
const trafficLight = document.querySelector('.traffic');
const notice = document.querySelector('.notice');
const controller = document.querySelector('.controller');
// 绑定点击事件
controller.onclick = function () {
// 绿变红
if (notice.innerHTML === '请通行') {
trafficLight.style.backgroundColor = 'red';
notice.innerHTML = '请等待';
controller.style.transform = 'rotatex(180deg)';
} else {
// 绿变红
trafficLight.style.backgroundColor = 'green';
notice.innerHTML = '请通行';
controller.style.transform = 'rotatex(0deg)';
}
};
复制代码
实现效果如下所示:
See the Pen
Bi-state Traffic Light 1 by Yiyang Sun (@syyCN)
on CodePen.
这种方式使用 JS 操作元素的内容和样式,显然这两者都不是 JS 应该负责的功能。
元素内容应该由 HTML 来负责,而样式部分应该由 CSS 负责。
此外,如果有其他人接手并维护这段代码,我们无法保证所有人都能读懂这一系列 JS 操作执行的意义。
四、做一些各司其责的尝试
我们不妨让 HTML 来负责元素内容,由 CSS 负责样式,而 JS 通过控制类名,达到控制显示效果的目的。
话不多说,先上代码。
<div class="traffic green">
<span class="notice"></span>
</div>
<div class="controller green">?</div>
复制代码
html,
body {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
}
.traffic {
width: 160px;
height: 160px;
border-radius: 50%;
// background-color: green;
display: flex;
justify-content: center;
align-items: center;
.notice {
color: white;
font-size: 2rem;
}
}
.traffic.green {
background-color: green;
.notice::after {
content: '请通行';
}
}
.traffic.red {
background-color: red;
.notice::after {
content: '请等待';
}
}
.controller {
position: absolute;
bottom: 10rem;
right: 180px;
font-size: 4rem;
transition: all ease-out 0.4s;
transform-origin: bottom;
cursor: pointer;
}
.controller.green {
transform: rotatez(0deg);
}
.controller.red {
transform: rotatez(180deg);
}
复制代码
const controller = document.querySelector('.controller');
controller.onclick = function () {
let trafficLight = document.querySelector('.traffic');
// 判断当前为绿灯或红灯,进行对应切换
if (trafficLight.classList.contains('green')) {
trafficLight.classList.remove('green');
trafficLight.classList.add('red');
controller.classList.remove('green');
controller.classList.add('red');
} else {
trafficLight.classList.remove('red');
trafficLight.classList.add('green');
controller.classList.remove('red');
controller.classList.add('green');
}
};
复制代码
整体的思路是:
- 预定义好 HTML 结构,以及每个 HTML 元素在不同类名之下的显示状态;
- 通过 JS 切换 HTML 的类名,使其匹配不同的 CSS 规则,从而达到显示内容切换的目的。
项目预览如下,功能均可实现。
See the Pen
Bi-state Traffic Light 2 by Yiyang Sun (@syyCN)
on CodePen.
温馨提示:
- 代码中使用了 伪类元素,因为
content
属性只在伪类元素中生效; - 经过这样的修改,我们代码的可读性增加了,JS 中写明了
red
以及green
类名,分别对应不同的灯的颜色; - 增加切换动画的功能不用涉及 JS,只需要在 CSS 中修改;
- 同样,如果需求变动,需要改成 “红黄灯”,也只需要改动 CSS 即可(如果你能接受
green
类名表示黄色 ?)。
这样的改进让我们获得了更高的代码可读性和可维护性,不过考虑一个问题,如果需要切换类名的元素巨多,而不是我们 demo 中的两个,那么 JS 代码又会变得冗长,从而可读性和可维护性减弱。
五、《世界上最好的设计,是没有设计》—— 雷总
听 “军” 一席话,如听一席话~
雷总讲的这个哲理,在前端领域同样适用。世界上最好的 JS 代码,是没有 JS 代码!(狗头保命 ?)
如果不用 JS,能否实现这样的切换呢?答案当然是——可以~
HTML 中有一个元素,刚好是实现双值切换的功能,它就是 checkbox
。选中与不选中,是两个状态,可以设置这两种状态对应的显示效果。
我们先来给 HTML 部分加上一个 checkbox
元素。
<div class="traffic">
<span class="notice">请通行</span>
</div>
<div class="controller">?</div>
<input type="checkbox" class="checkbox" />
复制代码
效果如下:
接下来,使用 CSS 将 checkbox
的选中与否的状态与红绿灯显示的状态关联。
html,
body {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
}
.traffic {
width: 160px;
height: 160px;
border-radius: 50%;
background-color: green;
display: flex;
justify-content: center;
align-items: center;
.notice:after {
content: '请通行';
color: white;
font-size: 2rem;
}
}
.controller {
position: absolute;
bottom: 10rem;
right: 180px;
font-size: 4rem;
transition: all ease-out 0.4s;
transform-origin: bottom;
cursor: pointer;
}
#checkbox:checked ~ .traffic {
background-color: red;
.notice:after {
content: '请等待';
}
}
#checkbox:checked ~ .controller {
transform: rotatez(180deg);
}
复制代码
现在就可以实现通过点击 checkbox
切换红绿灯的颜色了。但是这离我们的目标还有一定的距离,我们需要点击右下角的按钮实现切换。
只需要把按钮元素改为 label
标签,再加上一个 for
属性,其值为 checkbox
的 id
即可。
<input type="checkbox" id="checkbox" />
<div class="traffic">
<span class="notice"></span>
</div>
<label class="controller" for="checkbox">?</label>
复制代码
现在点击手闸,也可以实现红绿灯的切换。接下来,我们只需给 CSS 部分末尾加入以下代码,将 checkbox
元素隐藏即可。
#checkbox {
display: none;
}
复制代码
全部代码如下:
<input type="checkbox" id="checkbox" />
<div class="traffic">
<span class="notice"></span>
</div>
<label class="controller" for="checkbox">?</label>
复制代码
html,
body {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
font-size: 10px;
}
.traffic {
width: 160px;
height: 160px;
border-radius: 50%;
background-color: green;
display: flex;
justify-content: center;
align-items: center;
.notice:after {
content: '请通行';
color: white;
font-size: 2rem;
}
}
.controller {
position: absolute;
bottom: 10rem;
right: 180px;
font-size: 4rem;
transition: all ease-out 0.4s;
transform-origin: bottom;
cursor: pointer;
}
#checkbox:checked ~ .traffic {
background-color: red;
.notice:after {
content: '请等待';
}
}
#checkbox:checked ~ .controller {
transform: rotatez(180deg);
}
#checkbox {
display: none;
}
复制代码
这样,我们没有使用 JS,也实现了红绿灯切换的功能。这种思路还可以应用于其他具有双值切换需求的场景,例如深色浅色模式切换、LTR 或 RTL 阅读顺序切换等。
使用这种实现方式,优势在于我们没有写 JS 代码,所以在浏览器的兼容性方面表现较好。没有 JS 代码也就不会有 bug。
不足之处在于,相比于有 JS 的版本,这样的模式代码冗长,如果遇到更加复杂的逻辑,修改不便。
六、总结
世界上没有完美的代码,只有一直追求完美的工程师。在代码优化的过程中,我们的代码能力也在进阶。
第一次参加字节跳动的训练营,非常荣幸能听到 月影老师、李松峰老师 讲授的课程以及进行的答疑和交流。本次训练营注重基础,内容充实。在之后的学习中,我也需要更加注重自身基础的夯实。
感谢月影老师的 JS 课程,再次推荐月影老师的 掘金小册 – 前端工程师进阶 10 日谈。