无需JavaScript,有时也能解决问题!

发表时间: 2023-12-14 16:18



通过阅读本文,希望你能意识到在项目中可以减少JavaScript的使用。

原文链接:https://www.htmhell.dev/adventcalendar/2023/2/

未经允许,禁止转载!


作者 | Kilian Valkhof 译者 | 弯月
责编 | 夏萌
出品 | CSDN(ID:CSDNnews)

首先声明我并不讨厌JavaScript,相反,我很喜欢JavaScript,而且每天我都会编写大量的JavaScript。但我也喜欢CSS,甚至喜欢 HTML。我之所以喜欢这三种技术,是因为:最小权限原则。

最小权限原则

这是Web开发的核心原则之一,意思是说你应该在特定情境下选择最“弱”的语言。

在Web上,这意味着选择的优先顺序为:HTML > CSS > JavaScript。在这三种技术中,JavaScript是最灵活的语言,因为你可以描述浏览器应该如何运行,但JavaScript代码也可能出现故障、无法加载,而且需要额外的资源来下载、解析和运行。此外,使用JavaScript还会导致键盘用户和使用辅助技术的用户无法使用页面。

与JavaScript不同,HTML和CSS是声明式的。你告诉浏览器做什么,而不是怎么做。这意味着浏览器可以选择如何执行,并以最有效的方式执行。

由于HTML和CSS的特性由浏览器处理,因此更高效、更原生、更符合用户的偏好,而且更易访问。虽然有时也并非这般美好(尤其是在可访问性方面),但由浏览器替你完成繁重的工作,通常最终用户都会有更好的体验。

但我需要JavaScript

你可能会想:“我使用JavaScript正是因为我需要它。”话虽如此,但你要知道浏览器制造商和规范撰写者已经将许多功能转移到了CSS和HTML上,而在几年前这些功能还需要JavaScript。这正是本文所讨论的内容。

Web的棘手之处在于,在学会如何构建某个功能之后,就永远都不需要再学一次了。我们默认Web是向后兼容的(有极少数例外,但全世界第一个Web网页至今仍然可以在所有现代浏览器中正常运行)。

这也意味着,你学习到的解决方案都成为了你的工具箱的一部分,你可以反复使用,而且每次都能正常工作。因此,下面我所给出的示例都很酷,但我希望通过阅读本文,你能有所收获:虽然你知道一些功能需要JavaScript,但这不意味着如今仍然需要。你可以试试看,牢记这一点你就能制作出更好的网站。

自定义开关

我们的第一个例子是大家都非常熟悉的自定义开关。与普通的复选框不同,我们来设计要一个外观十分漂亮的开关。如果使用JavaScript,我们需要编写div、onclick处理程序和内部状态。但此处,我们将使用常规复选框和:checked伪类。以下是我们将要使用的HTML:

首先是一个label元素,里面包含一个复选框。这样做的好处是,浏览器已经为我们完成了很多工作。因为input位于label内部,浏览器已经将二者关联起来了,我们只需点击label的某处,就可以切换复选框,而无需onclick处理程序。浏览器免费为我们提供了这个功能。从功能上来说,我们已经做完了。

当然,设计师可不喜欢这个外观。下面,我们来添加一些CSS代码,创建一个拥有漂亮外观的自定义开关。

处的样式细节并不重要,但我希望你注意一下第二行代码:appearance: none。

表单元素,以及图片被称为“替换内容”。这意味着,它们并不真正属于HTML,而是由浏览器提供的。当浏览器渲染HTML并发现替换内容时,就会留下一个框,然后用实际内容替换该框。这就是为什么,例如,图片和表单元素不能使用伪元素的原因:当浏览器替换整个元素时,它们会被替换掉。

appearance 是告诉浏览器停止这种做法的一种方式。它告诉浏览器:“谢谢,但我想自己样式化我的表单控件。”这样,我们就可以使用::before 伪元素了。现在input本身就是开关的背景,而 ::before 伪元素是其中的切换点。

点击鼠标仍然可以切换复选框,但由于替换了元素,所以我们需要自己完成外观显示的工作。这里我们需要用到是:checked伪类:

时点击复选框时,:checked伪类开始工作,外观就会更新。

此处,我们使用原生HTML元素和一些CSS创建了一个拥有漂亮外观的自定义开关,但我们的工作还没有完成。对于鼠标用户,我们很清楚他们正在与哪个表单控件交互,但对于使用键盘的人来说,就没有那么容易了。

我相信你很熟悉下面这段CSS。为了摆脱那个丑陋的、点状的方框轮廓。

看到这里,你可能会说这种写法并不好。但我们应该怎样改进呢?在这方面,浏览器也已更新,能为我们提供更好的体验。如今,outline会沿着元素的border-radius显示,而且我们还可以将它设置到元素外部或内部:

此一来,当用户使用键盘与元素交互时(你可以尝试在点击复选框后按空格键,或者通过Tab键定位到它),:focus-visible就会匹配(使用鼠标时不会),然后你就能看到元素周围出现了一个漂亮的、蓝色的轮廓线。

最后,我希望你将outline: none替换为:

最后的结果相同:你看不见轮廓线,不是因为它被隐藏了,而是因为它是透明的。然而,打开高对比度模式(也称为强制颜色)的用户就可以看到这条轮廓线。因为在高对比度模式下,透明颜色会被用户选择的颜色替换,帮助他们看清楚正在与之交互的内容,即使他们使用鼠标。

数据列表,原生的自动建议

我希望在下一个项目中,当需要安装自动完成的框架时,你可以先试试看数据列表(datalist)。数据列表是浏览器内置的一个功能,可在用户输入时以列表的形式显示选项。

为了使用这个功能,你需要在HTML中添加一个带有ID和一组选项的数据列表元素。别担心,这个元素是不可见的。然后,你需要使用input的list属性将它们关联起来。

当用户在input中输入时,浏览器就会将数据列表显示为下拉菜单,并根据用户的输入自动过滤选项。由于它是一个常规输入,用户仍然可以输入自己的值。当然,他们也可以选择输入框并使用箭头键导航列表,或者点击浏览器添加的下拉图标来查看所有选项。

更强大的颜色选择器

我们见过很多具有漂亮外观的颜色选择器,它们由漂亮的画布UI和几百行的JavaScript代码构建而成。但你可知道你也可以使用原生的颜色选择器?

这行HTML就可以为你提供一个拥有漂亮的UI的颜色选择器,免去你编写大量的JavaScript。此外,由于这个颜色选择器是由浏览器处理的,我们还可以免费获得更多功能。在Chromium浏览器中,这个原生颜色选择器还可以让你自由选择颜色,不仅可以选择自己的网站,还可以从屏幕的任何地方选择颜色。非常强大!

需要注意的是,尽管浏览器显示了一个漂亮的颜色选择器,但并非所有的用户都可以使用。但提供另一种选择颜色的方式仍然是一个好主意。

折叠菜单

折叠菜单是以更有条理、更整洁的方式显示具有大量内容的页面的一种好方法,因为它可以将不必要的内容隐藏起来,当用户需要时再显示出来。而如今浏览器也提供了免费的折叠菜单,你可以通过details和summary元素实现:

默认情况下,details元素内的所有内容都是隐藏的,summary元素除外。当用户点击summary元素时,浏览器会显示其余内容。

通常你会看到折叠菜单的其中一项已是打开状态,而其他项是关闭的。你可以使用open属性来实现这一点:

如果你熟悉React,看到这段代码就会想:“这样很好,如今该元素有了open属性,就不会再关闭了”,然而实际情况并非如此。open属性只是初始状态,会随着用户的交互更新。

你也可以定义details元素的样式。那个小三角形(设计师一看到就想要替换)是一个::marker伪元素,你可以设置它的样式:

请记住,更改内容可能会影响辅助技术如何读取折叠菜单。此外,对于Safari,你需要使用伪元素::-webkit-details-marker。

伪元素marker无法像其他元素一样指定常见的样式(许多CSS属性对它无效,例如将其定位到完全不同的位置),但你可以替换其内容,例如使用表情符号,设置背景颜色或图像,以及更改字体大小。

我们可以通过open属性赋予打开状态和关闭状态不同的样式。

最后,我们想要对summary元素做一些处理。它是可点击的,但与链接不同,它没有指针光标,而且看起来也不像一个按钮。因此,我认为我们应该为它添加悬停和焦点状态,并帮助用户意识到它是可点击的:

此处,我不想讨论“只有链接应有指针光标”的问题,我想表达的主要观点是你需要做一些处理。

模态对话框

有时你需要向用户传达一些信息,或询问他们,或让他们确认一些事情。在JavaScript中,我们可以使用alert()、prompt()和confirm()。但它们有一个很大的缺点:它们锁定了主线程,这意味着页面不能做任何其他事情。此外,这些都是浏览器原生的功能,因此你不能根据自己的设计设置它们的样式。

另一方面,构建自己的对话框纯属自找麻烦:你需要保持焦点在对话框内,以确保可访问性,声明它的模态性,确保用户不会意外退出,而且还需要与z-index为2147483647的聊天小部件斗争(懂得都懂)。

因此,浏览器提供了一个原生的对话框元素:

这个元素默认情况下是不显示的,因此我们需要一些小技巧,而且还会用到JavaScript:

上面的代码可以在不使用JavaScript的情况下打开对话框,但这种写法还没有规范化。因此,目前我们需要使用JavaScript来打开对话框。但只有这个地方用到了JavaScript,其余代码都是原生HTML和CSS。

dialog元素有一个showModal()函数,我们可以利用它打开对话框。这个对话框是在top-layer上打开的,这是浏览器中的一个新概念。

这里的top layer是一个新层,不同于HTML,你可以将元素“提升”到该层。这意味着,位于top layer上的元素始终在所有其他元素之上,无论元素的z-index和上下文嵌套如何。

然而,你可能会注意到浏览器并没有提供任何UI。这个对话框基本上就是一个div(不是按钮),必须由你来提供关闭按钮。这就是上面代码中表单的作用。你可能已经注意到它有一个"dialog"方法。在提交该表单时,浏览器会将其视为关闭对话框的信号。

另外,你还可以创建确认对话框,只需提供两个按钮即可:

为了响应用户点击按钮,你需要通过监听对话框上的close事件,并读取returnValue属性:

如果对话框中还有其他表单数据,你还可以读取formData。

至于样式,由于该对话框本质上是一个div,你可以根据自己的需求设置样式。浏览器只负责自动将其放置在屏幕中央,其他一切由你决定。

另外,对话框还带有一个新的伪元素,名叫 ::backdrop。这是一个位于对话框和页面其余部分之间的层,你可以设置它的样式,例如,调暗页面的其余部分或以其他方式将用户的注意力引导到对话框上。举个例子,你可以覆盖一个白色层并模糊页面:

就像对话框元素本身一样,backdrop 的定位由浏览器处理,因此你无需担心滚动、固定元素和浏览器调整大小。这一切都由浏览器处理。

总结

希望通过阅读本文,你能意识到下一个项目可以减少JavaScript的使用。

这类的例子还有很多,下面仅列举一些:

  • 使用scroll-behavior: smooth实现原生的平滑滚动(仅当prefers-reduced-motion: no-preference匹配时)。

  • 使用scroll-snap创建原生的轮播。

  • 使用position: sticky创建"视图内"的元素。

  • 及容器查询的概念等。

另外,展望未来,我们还会看到更多有趣的东西:

  • 滚动驱动的动画。

  • 使用grid-template-rows: masonry实现瀑布流布局。

  • 使用新的selectlist元素实现完全可样式化的选择框(你可以样式化选择框的每个部分,而无需破坏其原生功能)。

  • :has()选择器将消除一大类JS选择。

最后,重申本文的主要观点:

虽然你知道一些功能需要JavaScript,但这不意味着如今仍然需要。你可以试试看,牢记这一点你就能制作出更好的网站。