整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

11 个可能你没见过,但非常特别的 HTML 标签

、abbr

abbr 全称是 abbreviations,意思是缩写。应用场景也很简单,为一些文章中的缩写增加注释。

以前在文章中对于缩写的解释经常会这么做:

DAU(Daily Active User),日活跃用户数 ......

那我们用 abbr 标签呢?

<abbr title="Daily Active User">
    DAU
</abbr>
<span>,日活跃用户数 ......</span>

展示的效果如下:

这个标签就可以把全称隐藏掉,弱化信息量,让真正不知道该缩写的用户主动去获取缩写的具体意思,这个在 markdown 里经常会出现。

二、mark

<mark/> 在 markdown 中也是很常用的,用于将包裹的文本高亮展示。

<mark>高亮文本</mark>

效果如下:

如果全文统一高亮样式,可以专门对 mark 标签进行样式重置,这样就不用对你用的每个 div 加一个 highlight 的类名了,又不语义化,又徒增文档大小。

三、sup、sub

<sup/><sub/>分别表示上标和下标,在 markdown 中出现得也很频繁,比如数学公式和引用。

<div>3<sup>[2]</sup></div>
<div>4<sub>2</sub></div>

效果如下:

上标和下标的样式原理也比较简单,主要就是利用了 vertical-aligntopsub 属性值,然后将字号缩小,不过有现成的标签,干嘛不用呢?

四、figure

figure 是用于包裹其它标签的内容的,然后再利用另一个标签 figcaption ,可以对包裹的内容进行一个文本描述,例如:

<figure>
    <img src="/media/cc0-images/elephant-660-480.jpg"
         alt="大象">
    <figcaption>这是一张大象的照片</figcaption>
</figure>

效果如下:

那要是图片挂了呢?

再友好点处理,我们把 img 标签的 alt 属性去掉。

漂亮,终于把我一直厌烦的图裂 icon 给干掉了,样式还巨好看。

当然不止能包裹 img 标签,其它任何都是可以的。

嘿嘿,给大家在本文来个实战,下面这个可以点击,样式也是利用了 figure 这个标签。

我是figure标签产生的

五、progress

说到 <progress/> 这个标签就很有意思了,去年有段时间我做的业务里涉及到了进度条,当时是前同事做的,然后有一些性能问题,我就在研究如何优化,减少进度条改变带来的性能问题。

虽然最后问题是解决了,但是也有幸收到了张鑫旭大佬的评论,他告诉我 progress 这个标签就足够了,既有语义化,又有进度条的功能,性能还好,兼容性也很不错。后来经过一番尝试,还真是,当时是我孤陋寡闻了,也安利给大家。

<!-- 进度条最大值为100,当前进度为60,即60% -->
<progress max="100" value="60"/>

浏览器自带的样式就已经很好看了,效果如下:

业务中我们也就可以通过控制 value 属性,来改变进度条的进度了。

六、area

area 这个标签也非常有意思,它的作用是为图片提供点击热区,可以自己规定一张图的哪些区域可点击,且点击后跳转的链接,也可以设置成点击下载文件,我们来举个例子:

  <img src="example.png" width="100" height="100" alt="" usemap="#map">

  <map name="map">
    <area shape="rect" coords="0,0,100,50" alt="baidu" href="https://www.baidu.com">
    <area shape="rect" coords="0,50,100,100" alt="sougou" href="https://www.sogou.com/">
  </map>

area 一般要搭配 map 标签一起使用,每个 area 标签表示一个热区,例如上面代码中,我们定义了两个热区,热区形状都为rect(矩形),他们的热区分别是:

  • 坐标 (0,0) 到坐标 (100,50) 的一个矩形
  • 坐标 (0,50) 到坐标 (100,100) 的一个矩形

我们都知道,默认的坐标轴是这样的:

因此,我们划分的两个热区就是:

最后再来看一下我们的实际效果:

i

七、details

details 字面意思是 "详情",在 markdown 里也经常用,用该标签包裹了的内容默认会被隐藏,只留下一个简述的文字,我们点击以后才会展示详细的内容。

<details>
  <p>我是一段被隐藏的内容</p>
</details>

效果如下:

这还没有加任何一行的 js 代码,我们点击后,details 标签上会多一个 open 的属性,被隐藏的内容就展示出来了。

默认情况下,简要文字为 "详情",想要修改这个文字,要搭配 summary 标签来使用。

<details>
  <summary>点击查看更多</summary>
  <p>我是一段被隐藏的内容</p>
</details>

就搞定了!

八、dialog

浏览器自带弹窗方法 alertconfirmprompt,样式固定且每个浏览器不同,同时还会阻塞页面运行,除了这个还提供了一个 dialog 标签,它的使用方式有点类似于现在各大组件库的 Modal 组件了,浏览器还为该标签提供了原生的 dom 方法:showModalclose,可以直接控制弹窗的展示和隐藏。

<dialog id="dialog">
    <input type="text">
    <button id="close">ok</button>
</dialog>
<button id="openBtn">打开弹框</button>

<script>
    const dialog = document.getElementById('dialog')
    const openBtn = document.getElementById('openBtn')
    const closeBtn = document.getElementById('close')
  
    openBtn.addEventListener('click', () => {
        // 打开弹框
        dialog.showModal()
    })
    closeBtn.addEventListener('click', () => {
        // 隐藏弹框
        dialog.close()
    })
</script>

效果如下:

细心的你有没有发现,这原生的弹框还自带背景蒙层,点击是关闭不掉的,但起码它不会阻塞页面。

然后我们在弹窗展示时,也可以通过 esc 键来关闭弹窗。

九、datalist

datalist 是用于给输入框提供可选值的一个列表标签,类似咱们常用的 Select 组件。

我可以用其实现一个 "输入联想" 的功能。

<label> 输入C开头的英文单词:</label>
<input list="c_words"/>

<datalist id="c_words">
  <option value="China">
  <option value="Click">
  <option value="Close">
  <option value="Const">
  <option value="Count">
</datalist>

来试一试:

刚点击时会把所有推荐的选项都列出来,然后根据后面输入的内容,会过滤掉不匹配的选项,比如我输入 cl,会过滤掉不是 cl 开头的单词,最后只剩下 ClickClose 了。

最后我发现,他这个下拉框有点好看啊?为啥这原生的 input 框默认样式那么丑,啥时候改改。

十、fieldset

fieldset 标签是用于分组管理 form 表单内的元素的,若 fieldset 设置了 disabled 属性,则被其包裹的所有表单元素都会被禁用置灰,且不会随着表单一起提交上去,是的就成了摆设。

什么意思呢?看个例子:

<form action="/example">
  <fieldset disabled>
    <legend>被禁用区域</legend>
    <label>ID:</label>
    <input type="text" name="id" value="1">
    <label>邮箱:</label>
    <input type="text" name="email" value="1234567@163.com">
  </fieldset>
  <label>名字:</label>
  <input type="text" name="name">
  <button type="submit">提交</button>
</form>

这里我们把 ID邮箱 的表单包裹了起来,且设置了 disabled,只开放了一个 name 的输入控件,此时界面如下:

可以看到除了 name 输入框,其它的两个输入框都被禁用了,此时点提交会是什么样子呢?

嗯,只提交了 name 字段。

十一、noscript

这个标签是在浏览器不支持或禁用了 javascript 时才展示的,大多用于对 js 强依赖的应用,比如现在大部分的 SPA 页面,一旦不支持 javascript,页面基本上什么内容都没了,此时可以靠这个标签做友好提示。

一般我们不需要特地去使用,大多都是在打包过程中自动插入到 html 静态文件里去的,例如:

// init.js
const root = document.getElementById('root')
const button = document.createElement('button')
button.innerText = '点击出弹窗'
root.appendChild(button)
<!-- index.html -->
<script defer src="./init.js"></script>

<noscript>
  不好意思,你的浏览器不支持或禁用了 JavaScript,请更换浏览器或启用 JavaScript
</noscript>

<div id="root"></div>

未禁用 javascript 时,页面是这样的:

禁用了 javascript 时,是这样的:



过阅读本文,希望你能意识到在项目中可以减少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,但这不意味着如今仍然需要。你可以试试看,牢记这一点你就能制作出更好的网站。

日常开发时,弹窗是一个经常使用的功能,而且重复性极高,你可能会遇到下面这些问题:
1、一个页面内多个弹窗, 要维护多套弹窗状态,看的眼花缭乱
2、弹窗内容比较简单,声明变量 + 模板语法的方式写起来比较麻烦

关于这些问题, 我首先想到的是应该弄一个即用即走的Dialog,不用去单独维护它的状态,使用Dialog({ xxx })这种形式去调用它,例如下面这种配置的方式:

Dialog({
    title: 'xxx',
    render: () => xxx
})


其中render可以是一个html字符串,也可以是jsx(需要配置对jsx的支持),这样可以对内容区域实现自定义。

通过配置项调用

各大主流的ui库基本都实现了这种调用方式:

  • Ant Design Vue的 Modal
  • Element-plus的 MessageBox

之前没有注意到Element-plus的MessageBox可以使用jsx,大部分场景下,用它来代替Dialog还是很方便的。
示例代码:

<script lang="jsx" setup>
import { reactive, ref } from 'vue';
import { ElMessageBox, ElForm, ElFormItem, ElInput } from 'element-plus'

const formRef = ref(null)
const form = reactive({ height: '', width: '' })
const rules = reactive({
  height: {
    required: true,
    trigger: 'blur'
  },
  width: {
    required: true,
    trigger: 'blur'
  }
})

function openMessageBox() {
  ElMessageBox({
    title: 'Message',
    showCancelButton: true,
    // message如果不是函数形式 绑定ref会失败
    message: () =>
      <ElForm
        ref={formRef}
        model={form}
        rules={rules}
      >
        <ElFormItem label="height" prop="height">
          <ElInput v-model={form.height}></ElInput>
        </ElFormItem>
        <ElFormItem label="width" prop="width">
          <ElInput v-model={form.width}></ElInput>
        </ElFormItem>
      </ElForm>
    ,
    beforeClose: (action, instance, done) => {
      console.log(action, instance)
      formRef.value && formRef.value.validate(status => {
        console.log('校验状态: ', status)
        if (status || action==='cancel') done()
      })
    }
  })
}
</script>

<template>
  <div class="container">
    <button @click="openMessageBox">
      打开messagebox
    </button>
  </div>
</template>


效果如下:

vueuse的createTemplatePromise

如果你不想使用jsx,而是想使用模板,vue的hooks工具库vueuse中提供了 createTemplatePromise 这个函数用来创建对话框、模态框、Toast 等,并且完全使用的是template的方式,因此自定义程度更高,并且没有任何额外成本(不需要jsx)。
下面是一个createTemplatePromise结合el-dialog的例子(当然也可以结合其它的dialog或者自定义dialog):

<script lang="jsx" setup>
import { createTemplatePromise } from '@vueuse/core'
import { ElDialog, ElButton } from 'element-plus'

const TemplatePromise = createTemplatePromise()

async function open(idx) {
  console.log(idx, 'Before')
  const result = await TemplatePromise.start('Title', `Hello ${idx}`)
  console.log(idx, 'After', result)
}

function asyncFn() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('ok')
    }, 1000)
  })
}
</script>

<template>
  <div class="container">
    <button @click="open(1); open(2)">
      打开两个弹框
    </button>
  </div>
  <TemplatePromise v-slot="{ resolve, args, isResolving }">
    <el-dialog :modelValue="true" :title="args[0]">
      <div>Dialog {{ args[1] }}</div>
      <p>可以打开控制台查看logs</p>
      <div class="flex gap-2 justify-end">
        <el-button @click="resolve('cancel')">
          取消
        </el-button>
        <el-button type="primary" :disabled="isResolving" @click="resolve(asyncFn())">
          {{ isResolving ? 'loading...' : '确认' }}
        </el-button>
      </div>
    </el-dialog>
  </TemplatePromise>
</template>


效果如图:

Promise化Dialog的优点

这样有小伙伴可能会说, 这看起来和原来用dialog也没有很大区别啊, 也要写模版 + 函数方法. 那么让dialog变成这样有什么好处呢?
1、最大的好处是弹窗变得可编程了, 通过函数调用的方式来控制UI. 不用再单独声明变量控制显隐, 也不用单独再去控制按钮的禁用、loading等状态. 例如以上的例子中, 我们可以轻松的处理button的loading状态(不用再额外声明变量), 用Promise让Dialog的UI和状态实现了內聚.
2、相比第一种方式, 对UI的可自定义程度更高.


作者:隔壁老王z
链接:https://juejin.cn/post/7293173815181738022