abbr 全称是 abbreviations,意思是缩写。应用场景也很简单,为一些文章中的缩写增加注释。
以前在文章中对于缩写的解释经常会这么做:
DAU(Daily Active User),日活跃用户数 ......
那我们用 abbr 标签呢?
<abbr title="Daily Active User">
DAU
</abbr>
<span>,日活跃用户数 ......</span>
展示的效果如下:
这个标签就可以把全称隐藏掉,弱化信息量,让真正不知道该缩写的用户主动去获取缩写的具体意思,这个在 markdown 里经常会出现。
<mark/> 在 markdown 中也是很常用的,用于将包裹的文本高亮展示。
<mark>高亮文本</mark>
效果如下:
如果全文统一高亮样式,可以专门对 mark 标签进行样式重置,这样就不用对你用的每个 div 加一个 highlight 的类名了,又不语义化,又徒增文档大小。
<sup/>和<sub/>分别表示上标和下标,在 markdown 中出现得也很频繁,比如数学公式和引用。
<div>3<sup>[2]</sup></div>
<div>4<sub>2</sub></div>
效果如下:
上标和下标的样式原理也比较简单,主要就是利用了 vertical-align 的 top 和 sub 属性值,然后将字号缩小,不过有现成的标签,干嘛不用呢?
figure 是用于包裹其它标签的内容的,然后再利用另一个标签 figcaption ,可以对包裹的内容进行一个文本描述,例如:
<figure>
<img src="/media/cc0-images/elephant-660-480.jpg"
alt="大象">
<figcaption>这是一张大象的照片</figcaption>
</figure>
效果如下:
那要是图片挂了呢?
再友好点处理,我们把 img 标签的 alt 属性去掉。
漂亮,终于把我一直厌烦的图裂 icon 给干掉了,样式还巨好看。
当然不止能包裹 img 标签,其它任何都是可以的。
嘿嘿,给大家在本文来个实战,下面这个可以点击,样式也是利用了 figure 这个标签。
我是figure标签产生的
说到 <progress/> 这个标签就很有意思了,去年有段时间我做的业务里涉及到了进度条,当时是前同事做的,然后有一些性能问题,我就在研究如何优化,减少进度条改变带来的性能问题。
虽然最后问题是解决了,但是也有幸收到了张鑫旭大佬的评论,他告诉我 progress 这个标签就足够了,既有语义化,又有进度条的功能,性能还好,兼容性也很不错。后来经过一番尝试,还真是,当时是我孤陋寡闻了,也安利给大家。
<!-- 进度条最大值为100,当前进度为60,即60% -->
<progress max="100" value="60"/>
浏览器自带的样式就已经很好看了,效果如下:
业务中我们也就可以通过控制 value 属性,来改变进度条的进度了。
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(矩形),他们的热区分别是:
我们都知道,默认的坐标轴是这样的:
因此,我们划分的两个热区就是:
最后再来看一下我们的实际效果:
i
details 字面意思是 "详情",在 markdown 里也经常用,用该标签包裹了的内容默认会被隐藏,只留下一个简述的文字,我们点击以后才会展示详细的内容。
<details>
<p>我是一段被隐藏的内容</p>
</details>
效果如下:
这还没有加任何一行的 js 代码,我们点击后,details 标签上会多一个 open 的属性,被隐藏的内容就展示出来了。
默认情况下,简要文字为 "详情",想要修改这个文字,要搭配 summary 标签来使用。
<details>
<summary>点击查看更多</summary>
<p>我是一段被隐藏的内容</p>
</details>
就搞定了!
浏览器自带弹窗方法 alert、confirm、prompt,样式固定且每个浏览器不同,同时还会阻塞页面运行,除了这个还提供了一个 dialog 标签,它的使用方式有点类似于现在各大组件库的 Modal 组件了,浏览器还为该标签提供了原生的 dom 方法:showModal、close,可以直接控制弹窗的展示和隐藏。
<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 是用于给输入框提供可选值的一个列表标签,类似咱们常用的 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 开头的单词,最后只剩下 Click 和 Close 了。
最后我发现,他这个下拉框有点好看啊?为啥这原生的 input 框默认样式那么丑,啥时候改改。
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 字段。
这个标签是在浏览器不支持或禁用了 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/
未经允许,禁止转载!
首先声明我并不讨厌JavaScript,相反,我很喜欢JavaScript,而且每天我都会编写大量的JavaScript。但我也喜欢CSS,甚至喜欢 HTML。我之所以喜欢这三种技术,是因为:最小权限原则。
这是Web开发的核心原则之一,意思是说你应该在特定情境下选择最“弱”的语言。
在Web上,这意味着选择的优先顺序为:HTML > CSS > JavaScript。在这三种技术中,JavaScript是最灵活的语言,因为你可以描述浏览器应该如何运行,但JavaScript代码也可能出现故障、无法加载,而且需要额外的资源来下载、解析和运行。此外,使用JavaScript还会导致键盘用户和使用辅助技术的用户无法使用页面。
与JavaScript不同,HTML和CSS是声明式的。你告诉浏览器做什么,而不是怎么做。这意味着浏览器可以选择如何执行,并以最有效的方式执行。
由于HTML和CSS的特性由浏览器处理,因此更高效、更原生、更符合用户的偏好,而且更易访问。虽然有时也并非这般美好(尤其是在可访问性方面),但由浏览器替你完成繁重的工作,通常最终用户都会有更好的体验。
你可能会想:“我使用JavaScript正是因为我需要它。”话虽如此,但你要知道浏览器制造商和规范撰写者已经将许多功能转移到了CSS和HTML上,而在几年前这些功能还需要JavaScript。这正是本文所讨论的内容。
Web的棘手之处在于,在学会如何构建某个功能之后,就永远都不需要再学一次了。我们默认Web是向后兼容的(有极少数例外,但全世界第一个Web网页至今仍然可以在所有现代浏览器中正常运行)。
这也意味着,你学习到的解决方案都成为了你的工具箱的一部分,你可以反复使用,而且每次都能正常工作。因此,下面我所给出的示例都很酷,但我希望通过阅读本文,你能有所收获:虽然你知道一些功能需要JavaScript,但这不意味着如今仍然需要。你可以试试看,牢记这一点你就能制作出更好的网站。
自定义开关
首先是一个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库基本都实现了这种调用方式:
之前没有注意到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>
效果如下:
如果你不想使用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>
效果如图:
这样有小伙伴可能会说, 这看起来和原来用dialog也没有很大区别啊, 也要写模版 + 函数方法. 那么让dialog变成这样有什么好处呢?
1、最大的好处是弹窗变得可编程了, 通过函数调用的方式来控制UI. 不用再单独声明变量控制显隐, 也不用单独再去控制按钮的禁用、loading等状态. 例如以上的例子中, 我们可以轻松的处理button的loading状态(不用再额外声明变量), 用Promise让Dialog的UI和状态实现了內聚.
2、相比第一种方式, 对UI的可自定义程度更高.
作者:隔壁老王z
链接:https://juejin.cn/post/7293173815181738022
*请认真填写需求信息,我们会在24小时内与您取得联系。