前端界有哪些越早知道越好的小技巧、小知识?
来分享下 鹅厂前端开发工程师 @jesonliao 总结的当前前端界的些新知识。
本文介绍一些 Web 平台的新技术。这些技术不只是尝鲜,而是已经达到可用阶段,用到生产实践中去可以切实提升开发效率和用户体验。本文内容主要选取自 2022 Google I/O 大会的视频 What's new for the web platform。
目录
accent-color<dialog>datetime-localBack/forward cacheloading="lazy"aspect-ratioCSS Containmentcontent-visibilityPriority Hintssize-adjustWebAuthnstructuredCloneTop level awaitPrivate filedsarray.at()SharedArrayBufferCSS cascade layersCSS :has()参考
accent-color
介绍
新增 CSS 属性accent-color
,用于修改表单元素的颜色。
表单元素例如<input>
<checkbox>
<radio>
等,它们的样式是由用户代理样式表(user agent stylesheet)决定的,各自浏览器的实现都不一样。
浏览器自带的样式都比较朴实无华,而且修改表单样式是很困难的。以前的做法通常是在表单元素上覆盖或者包裹一些普通元素(div、span)来定制样式。比如 Ant Deisign [1]里的 CheckBox 就是盖了个<span>
标签来控制样式。
用法
有了accent-color
就可以更方便地给表单元素设置主题色。例如给所有表单元素设置 deeppink 颜色,只需要在根元素应用该属性:
::root { accent-color: deeppink;}
使用系统 API 的好处是浏览器会帮你处理好很多状态,这是自定义 UI 容易忽略的问题。例如可以自动适配暗黑模式。下面是添加accent-color: deeppink
后分别在 light 和 dark 模式下的表现。
兼容性
<dialog>
是原生支持的对话框元素,用法跟很多 UI 组件库里的 Modal、Dialog 组件类似。
用法
<dialog id="dialog"> <form method="dialog"> <p>Hi, I'm a dialog.</p> <button>OK</button> </form></dialog><button onclick="dialog.showModal()">Open Dialog</button>
默认<dialog>
元素是隐藏的,可以通过设置 HTML 属性open=true
,或者使用 .show()
和.showModal()
来展示对话框内容。
如果<dialog>
元素里有<form>
表单元素并且设置了method=dialog
,那么提交 form 表单时会关闭对话框并且把表单内容作为返回值。
优点
原生实现的<diglog>
有几大优点:
完善的无障碍能力;
完备的焦点处理。例如关闭对话框后能回到上一个焦点上;打开后自定聚焦到表单元素上;
强大的样式支持。通过 CSS 能快速实现动画、屏幕自适应等;
总之我们不再需要第三方库,也不用写复杂的控制脚本,就能实现对话框功能了。现在所有浏览器的最新版本都已经支持<dialog>
元素。
兼容性
datetime-local
介绍
<input>
标签拥有了一种新的类型:datetime-local
,用于同时输入日期和时间。
<input type="datetime-local">
目前用用于输入时间的控件有三种:
type=date:输入日期
type=time:输入时间
type=datetime-local:输入日期和时间
此外它还支持这些属性:
max:最大时间
min:最小时间
step:使用递增时的间隔
兼容性
浏览器支持程度已经相当高,又少了一项使用第三方 UI 组件库的必要。
Back/forward cache
介绍
Back/forward cache 简称 bfcache,是 Chrome 浏览器的一项优化。页面在离开时会先被缓存,浏览器前进或者后退操作时能直接使用。Firefox 和 Safari 多年前就支持了,Chrome 终于跟上。
页面在进入 bfcache 时,所有页面状态会被“冻结”,页面快照(包括 JS 运行状态)被放入在内存中,关闭浏览器后缓存会销毁。
在进入和离开页面时会触发事件pageshow
和pagehide
,并且加了persisted
属性来区分是页面是否使用 bfcache。
window.addEventListener('pagehide', (event) => { if (event.persisted) { console.log('页面进入后台,bfcache 生效'); } else { console.log('页面被销毁了,bfcache 未生效'); }});window.addEventListener('pageshow', (event) => { if (event.persisted) { console.log('页面从 bfcache 中恢复'); } else { console.log('页面正常加载'); }});
用法
这是浏览器做的优化,理论上页面开发者不需要进行额外操作就可以享受。但是并非所有页面都能支持 bfcache,要使 bfcache 生效,有以下几条准则。
1. 不要使用 unload
事件
unload
事件的原意是建立在页面被销毁的基础上,如果页面触发了unload
后进入了 bfcache 而没有被销毁,可能会导致很多已有的页面功能不符合预期。为了不让 bfcache 造成 breaking change(破坏性改动),浏览器检测到监听了unload
事件的页面就不会启用 bfcache。 可以使用pagehide
事件来替代unload
,无论 bfcache 是否生效,pagehide
事件都会触发。
2. 有条件地使用 beforeunload
事件
限制beforeunload
事件的原因跟unload
事件一样。但是我们有时必须使用到beforeunload
事件,例如在用户填写了表单还未提交的情况下离开页面,我们可以通过拦截beforeunload
事件给用户一个提示:“页面未保存,是否离开?”。 解决办法是要有条件地监听beforeunload
事件,而且一定要及时取消监听。
function beforeUnloadListener(event) { event.preventDefault(); return event.returnValue = 'Are you sure you want to exit?';};// A function that invokes a callback when the page has unsaved changes.onPageHasUnsavedChanges(() => { window.addEventListener('beforeunload', beforeUnloadListener);});// A function that invokes a callback when the page's unsaved changes are resolved.onAllChangesSaved(() => { window.removeEventListener('beforeunload', beforeUnloadListener);});
3. 避免引用 window.opener
使用window.open()
方法打开的页面,可以通过window.opener
访问前一个页面。例如:在 window A 中打开了 window B,B.opener 返回 A。我们称页面 B 具有window.opener[2]的引用,这种页面无法被放入 bfcache 中。如果一定要用window.open()
方法打开页面,可以加上
4. 页面离开前记得关闭连接
浏览器检测到页面在离开时有以下连接存在的话,就不进入 bfcache:
连接中的 indexDB正在进行的 fetch 或者 XMLHttpRequest 请求连接中的 WebSocket 或者 WebRTC
可以在pagehide
事件时关闭以上连接。
Chrome DevTools 里面可以看到当前页面是否支持 bfcache。打开Application > Back-forward Cache面板,点击“Run Test”按钮,如果提示“Restored from back-forward cache”就代表成功使用 bfcache。
loading="lazy"
介绍
Lazy-loading 俗称懒加载,指的是当页面滚动到元素的位置时才开始加载。
这是前端常用的加载速度优化手段之一,以前需要借助第三方库来实现,通过监听滚动事件不断计算元素的位置,对性能会有影响。
现在<img>
和<iframe>
都原生支持属性loading="lazy"
啦,由浏览器来处理就不用再担心性能问题。
用法
loading
属性有以下值:
eager
:默认值。立刻加载资源;
lazy
:延迟加载,等元素即将出现在视窗时再加载;
<img src="image.png" loading="lazy" alt="…" width="200" height="200">
使用loading="lazy"
时应该注意:
<img>
标签应该包含width
和height
属性,这样浏览器才能准确计算元素位置,从而判断加载时间。
避免在第一屏使用loading="lazy"
,这样可能会导致“负优化”效果。
兼容性
aspect-ratio
介绍
设置一个宽高比固定的元素在以前是一件麻烦的事情,新增的 CSS 属性aspect-ratio
就是为了解决这个问题。
以前的做法是利用 padding-top
的百分比特性。当设置padding-top
的单位是百分比时,它的参考对象是父元素的宽度。为了图片等元素应用宽高比,我们还需要一个额外的容器元素。
比如我们要实现一个图片的宽度自适应且宽高比固定为 16:9。
<div class="parent"> <div class="container"> <img src="" alt=""> </div></div>
使用padding-top
,需要靠容器和绝对定位:
/* 16:9 比例容器 */.container { width: 100%; padding-top: 56.25%; /* 9/16 */ background-color: pink; position: relative;}.container img { width: 100%; height: 100%; position: absolute; top: 0; left: 0;}
使用aspect-ratio
就要简单得多了:
.container img { width: 100%; aspect-ratio: 16 / 9;}
用法
aspect-ratio
可以用在任意元素中,格式为<number> / <number>
,即宽度和高度的比例。
值得注意的是aspect-ratio
的优先级比较低。当aspect-ratio
和其他属性例如width
height
、min-width
、min-height
产生了冲突的话,会以后者为准。
兼容性
CSS Containment
介绍
CSS Containment 是为了提升页面性能的一项 W3C 规范,定义了 CSS 属性 contain[3]。
**MDN**[4]: CSS Containment 主要是通过允许开发者将某些子树从页面中独立出来,从而提高页面的性能。如果浏览器知道页面中的某部分是独立的,就能够优化渲染并获得性能提升。 这个定义通过一个 CSS 属性 contain[5]来实现。
这个属性让开发者可以指定布局和渲染的边界,帮助浏览器尽可能减少重排和重绘。主要功能为:
通过隔离子树(isolating subtree)提升渲染性能;
修改子树内容不会影响到外部
用法
CSS Containment 规范定义了contain
属性有以下值:
layout
:该元素的内部布局与页面的其他部分完全隔离,内部不受外界任何东西的影响,同时也影响不了外部。
paint
:内部元素的绘制不会超出该元素,超出的部分会不可见。
size
:该元素的渲染不用去检测内部元素,即跟内部元素的尺寸无关。
style
(不常用):设置 counters[6] 和 quotes[7]两个属性不会影响到外部。
还有另外两个值,是其他属性的组合:
content
: layout + paint 的结合
strict
: layout + paint + size 的结合
示例
假如页面中有 10000 个元素这样的元素:
<div class="item"> <div>Lorem ipsum...</div></div>
如果你修改了第一个元素的内容(例如把“Lorem ipsum”改成“Hello World”),浏览器就要遍历整个 DOM 树后重新绘制。而如果你给每个元素加上 contain: strict
属性,那么浏览器只需要重绘你修改的元素。
这样对页面性能的提升是非常大的,可以打开这个DEMO 页面[8]感受一下。
兼容性
content-visibility
介绍
CSS 属性content-visibily
也是CSS Containment[9] 最新草案的一部分,用于优化页面渲染速度。
设置content-visibbily
属性可以暂时跳过元素的渲染(包括布局和绘制),直到它需要被用到的时候再进行渲染。通过这个属性,我们可以实现优先渲染首屏内容,页面其他部分先暂停渲染,这样可以极大地加速首屏展示时间,让用户更快体验到页面。
用法
content-visibility
具有三个值:
visible
:默认值,不产生影响,元素正常渲染。
hidden
:该元素内容会被跳过。
auto
:当元素不可见(且没有交互操作)时会跳过元素内容的渲染,需要的时候再渲染。
auto
是最常用来优化的值。它首先会让元素的内容独立渲染,相当于使用了前面说到的contain: layout + paint + style
;当元素不可见的时候,还会有contain: size
的效果(当前元素的渲染不用去检测子元素)。简单来说就是,当元素离屏的时候不渲染(包括 layout 和 paint)元素内容,也即跳过。当元素出现在视图中时,浏览器会移除contain: size
属性并开始渲染内容。
设置了content-visibility: auto
的元素,在离屏时只是不会渲染,但是会在 DOM 里,可以通过 DOM API 或者网页搜索功能找到。
content-visibility: hidden
代表不渲染该元素。它和display: none
还有visibility: hidden
有什么区别呢?
display: none
:DOM 树中移除该元素,layout 和 paint 都不参与。缺点是切换到展示状态时需要的代价较大。
visibility: hidden
:DOM 树中保留该元素,且会参与 layout,只是在图形绘制上隐藏。切换到展示状态需要的代价较低。但是由于该元素需要参与 layout,内容变更时会影响到外部,所以总体页面的渲染耗时并没有减少。
content-visibility: hidden
:DOM 树中保留该元素,不参与 layout 和 paint,页面渲染时会跳过该元素。但是它可以保留渲染状态,切换到展示状态时的代价很低。
示例
content-visibility: auto
可以用于优化首屏渲染速度,可以给内容较长、图片较多的页面,分区设置该属性。这篇介绍文章[10]的示例中提到,将content-visibility: auto
应用于分块内容区域,在初始加载时可获得 7 倍的渲染性能提升。
content-visibility: hidden
可以用于虚拟滚动列表、单页应用(SPA)的路由切换。可以减少渲染性能损耗,当需要展示时又可以快速渲染出来。
兼容性
Priority Hints
介绍
Priority Hints(优先级提示)用于告诉浏览器相关资源的优先级,让浏览器调整资源的加载顺序从而优化页面的加载体验。Priolity Hints 的内容包括 HTML 标签属性fetchpriority
和 JavaScript 的 Fetch API 里的priority
参数。
优先级提示是对现有浏览器资源加载优先级的补充。浏览器计算资源的优先级时会考虑这些因素:
资源类型,比如 CSS、Font、Scripts、Images 等等不同类型资源具有不同的优先级。
引用资源的代码顺序。
使用preload
标签属性。
使用async
和defer
标签属性。
当以上因素对资源优先级控制的精细度不够时,就需要用到 Priority Hints。Priority Hints 可以认为是在同等优先级的基础上进行调整。
用法
fetchpriority
属性可以用在link
, img
, script
, and iframe
标签上。它有三个选项值:
hign
:希望浏览器提高该资源的优先级。
low
:希望浏览器降低该资源的优先级。
auto
(默认):由浏览器自己决定优先级。
Fetch API priority
也可以传入上述三个选择值。
使用fetchpriority
标签属性和 Fetch API 的priority
参数的示例如下:
<!-- We don't want a high priority for this above-the-fold image --><img src="/images/in_viewport_but_not_important.svg" fetchpriority="low" alt="I'm an unimportant image!"><!-- We want to initiate an early fetch for a resource, but also deprioritize it --><link rel="preload" href="/js/script.js" as="script" fetchpriority="low"><script> fetch('https://example.com/', {priority: 'low'}) .then(data => { // Trigger a low priority fetch });</script><!-- The third-party contents of this iframe can load with a low priority --><iframe src="https://example.com" width="600" height="400" fetchpriority="low"></iframe>
兼容性
目前在只有基于 Chromium 的浏览器支持fetchpriority
属性。
size-adjust
介绍
size-adjust
是新增的 CSS@font-face
描述符,作用是调整字形大小,以便让不同字体获得一致的表现。
为什么需要它?不同字体的字形大小是不一样的,即使设置了相同的font-size
,效果也不一样。设置font-size: 64px
后不同字体的展示效果如下:
用法
size-adjust
需要放在@font-face
内,用来修饰选定字体。接受一个百分比作为值,代表该字体的缩放比例。
size-adjust: <percentage>
示例
给 Arial 字体加上size-adjust
属性后,字体放大了。
<h1>Size Adjust</h1><h1 class="adjusted">Size Adjust</h1>@font-face { font-family: "Arial"; src: local(Arial);}@font-face { font-family: "Size Adjusted Arial"; src: local(Arial); size-adjust: 150%;}h1 { font-size: 64; border: 1px solid #999;}h1.adjusted { font-family: "Size Adjusted Arial";}
兼容性
WebAuthn
介绍
WebAuthn,即 Web Authentication,一个用于在浏览器上进行认证的 API,是 W3C 的推荐标准(《Web Authentication: 一个用于访问公钥凭证的 API》[11])。
与传统的由服务端储存密码(或者密码的 hash)不同,WebAuthn 采用的是非对称加密的认证方式,服务端储存的是公钥,对应的私钥由客户端保管。
非对称加密的基础是一对公私和私钥,特点是公钥加密后的密文必须由私钥解密,反之私钥加密后必须由公钥解密。利用这个特点可以用来验证对方的身份,类似 HTTPS 的认证方式。流程大致如下:
注册阶段:由客户端(认证器)生成一对公私钥,服务器储存公钥,私钥存在客户端(认证器)中。
认证登录:服务器发送一段文本到客户端,客户端用私钥加密后再传给服务端,服务端用储存的公钥进行解密,并验证解密后的内容是否与原文一致。
认证器(Authenticator)有两类:
平台特定的认证器(Platform authenticators):由当前设备提供的认证方式,例如指纹识别、人脸识别等。
跨平台认证器(Cross platform authenticators):例如 USB Key,或其他支持 FIDO 协议的硬件设备。
可以在这里示例网站 https://webauthn.io/[12] 体验一下。
使用 WebAuthn 的优点有:
足够安全。整个认证过程私钥都存在客户端(认证器)中,没有传输丢失风险。而且即使服务端的公钥泄漏了,也不会造成影响。
方便。用户不需要记忆密码,可以借助设备的生物认证传感器直接登录。
用法
注册阶段通过浏览器接口navigator.credentials.create()
创建秘钥。
const publicKeyCredentialCreationOptions = { challenge: Uint8Array.from( randomStringFromServer, c => c.charCodeAt(0)), rp: { name: "Duo Security", id: "duosecurity.com", }, user: { id: Uint8Array.from( "UZSL85T9AFC", c => c.charCodeAt(0)), name: "lee@webauthn.guide", displayName: "Lee", }, pubKeyCredParams: [{alg: -7, type: "public-key"}], authenticatorSelection: { authenticatorAttachment: "cross-platform", }, timeout: 60000, attestation: "direct"};const credential = await navigator.credentials.create({ publicKey: publicKeyCredentialCreationOptions});
登录认证时通过接口navigator.credentials.get()
获取校验内容。
const publicKeyCredentialRequestOptions = { challenge: Uint8Array.from( randomStringFromServer, c => c.charCodeAt(0)), allowCredentials: [{ id: Uint8Array.from( credentialId, c => c.charCodeAt(0)), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }], timeout: 60000,}const assertion = await navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions});
兼容性
WebAuthn 的兼容性已经非常不错,可以在一些新兴的网站上使用。
structuredClone
介绍
新增函数structedClone()
用于深拷贝。
对于引用类型的值,拷贝方式分为浅拷贝和深拷贝,浅拷贝就是只复制了对象的外层属性,通常用扩展运算符进行操作:
const myOriginal = { someProp: "with a string value", anotherProp: { withAnotherProp: 1, andAnotherProp: true }};// 浅拷贝const myShallowCopy = {...myOriginal};
深拷贝需要递归地复制每一层属性,一个简单的深拷贝函数可以用递归实现:
function deepCopyObj(obj) { if (null == obj || "object" != typeof obj) return obj; if (obj instanceof Date) { var copy = new Date(); copy.setTime(obj.getTime()); return copy; } if (obj instanceof Array) { var copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = deepCopyObj(obj[i]); } return copy; } if (obj instanceof Object) { var copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = deepCopyObj(obj[attr]); } return copy; } throw new Error("Unable to copy obj this object.");}
自己写深拷贝函数需要考虑的情况很多,通常经不住考验。更推荐的做法是使用 lodash 里的_.deepCopy()
函数。
还有更加加单直接的方式,就是直接使用JSON.parse(JSON.stringify())
:
const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));
这种方式更像是一种作弊:把对象转换成了字符串,再转换回来。由于这种方式非常常用,所以 V8 引擎专门对JSON.parse()
做了优化来加速这种拷贝方式。虽然执行效率不是问题,但这种方式还是有很多弊端。以下场景都不能用JSON.parse(JSON.stringify())
:
递归的数据结构:遇到递归的数据结构JSON.stringify()
会抛异常;
一些内置 JS 类型:比如Map
, Set
, Date
, RegExp
or ArrayBuffer
,遇到这些类型JSON.stringify()
也会抛异常;
函数:JSON.stringify()
会忽略函数;
现在structuredClone()
来了,内置的深拷贝函数,执行效率高,而且支持各种 JS 类型。
用法
const myDeepCopy = structuredClone(myOriginal);
structuredClone()
支持拷贝递归的数据结构,所有原始类型(除了 Symbol)和绝大部分内置类型都能拷贝。但是它有以下限制:
不能拷贝函数;
不能拷贝 DOM nodes;
拷贝对象时会丢弃以下内容:
RegExp 对象的lastIndex
属性;
属性的 descriptors(描述),例如 getters、setters、readable 等;
原型链不会被拷贝,新对象的 prototype 都会指向 Object;
structedClone()
函数不是来自 ECMA 规范,而是一项 W3C 提案,但是各大 JS 平台都已经支持,包括Node.js。
兼容性
Top level await
介绍
JavaScript 支持在最外层使用await
关键字了。
以前,await
必须放在acync
函数体内,否则会报错。
await Promise.resolve(console.log(' '));<br/>// → SyntaxError: await is only valid in async function(async function() { await Promise.resolve(console.log(' ')); // → }());
现在可以直接在最外层使用await
。
await Promise.resolve(console.log(' '));<br/>// →
用法
注意必须在模块中使用顶级的await
,传统的脚本引入方式并不适用。
<script type="module"> import { processData } from "./utils.js"; const dataResponse = await fetch('/data'); processData(await dataResponse.json());</script>
兼容性
Private fileds
介绍
JavaScript 中的类(class)支持私有成员了。
在成员变量名前加上#
符号标识该成员为私有。支持私有属性、私有方法以及静态私有属性和方法。注意不是用 private 关键字哦。
用法
// 私有属性class ClassWithPrivateField { #privateField;}// 私有方法class ClassWithPrivateMethod { #privateMethod() { return 'hello world'; }}// 静态私有属性class ClassWithPrivateStaticField { static #PRIVATE_STATIC_FIELD;}// 静态私有方法class ClassWithPrivateStaticMethod { static #privateStaticMethod() { return 'hello world'; }}
兼容性
array.at()
JavaScript 数组中新增了 at()
方法,传入下标获取该项的值。at()
方法允许传入负数,代表从最后一个元素开始反向计数。
用法
const array = [5, 12, 8, 130, 44];console.log(array.at(1));// -> 12console.log(array.at(-2));// -> 130
array.at()
和array[]
相比没有什么特别之处,最大的区别是前者可以传入负数。对于要取数组最后一个元素的操作会方便很多:
// 以前const last = array[array.length - 1];// 现在const last = array.at(-1);
兼容性
SharedArrayBuffer
介绍
SharedArrayBuffer
即共享内存,可以实现不同线程之间共享内存,增加线程之间的通信效率。可用于 Web Worker 和 WebAssembly 的场景。
SharedArrayBuffer
的历史很复杂。它早在 2017 年就被很多浏览器实现,但是在 2018 年因为幽灵漏洞[13]而被禁用。随后 Chrome 68(2018 年 7 月)重新启用了SharedArrayBuffer
,通过站点隔离——给每个网页分配一个独立的进程——的方式来避免漏洞。但是这种方式消耗的资源过高所以只在 Chrome 桌面版中支持。到了 2020 年,新的标准提出了更安全的使用方式——在跨源隔离环境下才能使用SharedArrayBuffer
。
跨源隔离
为了减少某些 Web API 带来的漏洞风险,浏览器提供了一个可选加入的环境 ,称为“跨源隔离”(cross-origin isolated)。在这种环境下网页能够使用一些特权 API,包括SharedArrayBuffer
、performance.measureUserAgentSpecificMemory()
等。
跨源隔离通过 COOP(跨源打开程序策略 )和 COEP (跨源嵌入器策略)来开启。你需要在返回主文档的 HTTP 中加上这两个头部字段:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
跨源隔离环境下会有相应的限制:
COOP(跨源打开程序策略):当前页面使用window.open()
方式打开一个不同源页面后,窗口之间无法进行交互。例如无法通过window.opener
获得前一个窗口的引用。
COEP(跨源嵌入器策略):要求页面所引用的全部资源(包括 js、img、video 等)如果是不同源的话,必须经过明确授权。授权方式可以是使用 CORP 或 CORS。
用法
SharedArrayBuffer
与ArrayBuffer
的接口一致,但是前者的内存地址是在共享内存区块中。在 Web Worker 中的使用方式如下:
// 主线程// 新建 1KB 共享内存const sharedBuffer = new SharedArrayBuffer(1024);// 主线程将共享内存的地址发送出去w.postMessage(sharedBuffer);// 在共享内存上建立视图,供写入数据const sharedArray = new Int32Array(sharedBuffer);// Worker 线程onmessage = function (ev) { // 主线程共享的数据,就是 1KB 的共享内存 const sharedBuffer = ev.data; // 在共享内存上建立视图,方便读写 const sharedArray = new Int32Array(sharedBuffer); // ...};
兼容性
CSS cascade layers
介绍
CSS Cascade Layers,也叫做CSS 级联层,是Cascading and Inheritance Level5[14] 规范中新增的一个 @ 规则(at-rule
),对应的 CSS 属性写法@layer
。CSS 级联层是用来控制 CSS 权重(Specificity)的。
Cascade layers 可以理解为将 CSS 样式划分到不同的层上,层与层之间有权重比较,优先级更高的层会覆盖优先级低的层的样式。层内部的优先级按照原有的 CSS 选择器优先级进行计算。也就是说,如果层 A 的优先级比层 B 更低,那么定义在层 A 里的样式规则,无论优先级有多高,都比不过定义在层 B 里的样式规则。
通过将 CSS 合理地分层,可以防止意外的样式覆盖,并且可以更好地管理 CSS 的结构。
用法
使用层的方式很简单,将 CSS 样式规则用@layer <layer_name> {}
嵌套起来就可以了。
@layer layer_name { h1 { color: blue; }}
也可以不写名字,创建匿名层:
@layer { h1 { color: blue; }}
可以先声明层,然后再定义层内的样式规则:
@layer base;@layer base { # ...}
也可以一次性声明多个层:
@layer theme, layout, utilities;
还可以通过@import
导入文件时创建层,这个非常有用:
@import './theme.css' layer(theme);
层的声明顺序代表了层的优先级。越后声明的层优先级越高。注意的是,未使用层的样式优先级高于使用了层的样式。
示例
一个最常见的用法是将不同功能的样式划分到不同的层上去。例如:
/* 预先建立层顺序,从最低到最高优先级 */@layer reset, theme, components, utilities;/* 重置 */@layer reset {}/* 主题样式 */@layer theme {}/* 组件样式 */@layer components {}/* 功能样式 */@layer utilities {}
更合理的组织方式是将样式放入不同的文件中,通过@import
引入:
/* 预先定义层的顺序 */@layer base, theme, layouts, components, utilities;/* Base */@import '../styles/base/normalize.css' layer(base); /* normalize or rest file */@import '../styles/base/base.css' layer(base); /* body and base styles */@import '../styles/base/theme.css' layer(theme); /* theme variables */@import '../styles/base/typography.css' layer(theme); /* theme typography */@import '../styles/base/utilities.css' layer(utilities); /* base utilities *//* Layouts */@import '../styles/components/post.css' layer(layouts); /* post layout *//* Components */@import '../styles/components/cards.css' layer(components); /* imports card */@import '../styles/components/footer.css' layer(components); /* footer component */
兼容性
CSS :has()
介绍
CSS 伪类:has()
是一个功能非常强大的 CSS 选择器,它的含义是“选择包含选定元素的元素”。这将是 CSS 最重要的更新之一。
:has()
伪类很早就出现在规范中,但是由于性能问题,规定只能在document.querySelector()
这样的 DOM 方法中使用。今年来浏览器开始大力支持,可以在 CSS 中直接使用了。因为它可以实现类似“父选择器”和“前面兄弟选择器”的功能,对 CSS 的开发会有颠覆性的影响。
用法
:has()
接受一个选择器作为参数,如果某个元素有后代(相对于该元素的 :scope[15])能匹配传入的选择器,那么该元素会被选择。
:has( <forgiving-relative-selector-list> )
示例
选择子元素是图片的<a>
标签,相当于“父选择器”:
/* 子元素是图片的a标签 */a:has(> img) { display: block;}
选择后面是<p>
标签的<h5>
,相当于“前面兄弟选择器”:
/* 后面是p标签的h5 */h5:has(+ p) { font-size: 1rem;}
:has()
会颠覆很多样式的写法。例如给校验通过的表单一个绿色边框,以前需要通过 JS 来控制样式,现在只需要 CSS 就行:
/* 提交按钮未被禁用的表单 */form:has(.submit:not(:disabled)) { border-color: green;}
兼容性
目前:has()
的兼容性并不好,但是各大浏览器对它的支持已经提上日程,相信很快就能用上。
参考
What's new for the web platform: https://www.youtube.com/watch?v=5b4YcLB4DVI[16]
CSS accent-color: https://goo.gle/399xjOz[17]
CSS color-scheme: https://goo.gle/3N1kpRe[18]
dialog: https://goo.gle/3L07LjR[19]
selectmenu: https://goo.gle/3M5jVt8[20]
Selectmenu demos: https://goo.gle/3wftMGh[21]
Input datetime-local: https://goo.gle/3ysFCzj[22]
COLRv1 fonts: https://goo.gle/3L2zeS3[23]
Back/forward cache: https://goo.gle/39SJgbJ[24]
loading="lazy": https://goo.gle/3w3DigU[25]
CSS aspect-ratio: https://goo.gle/3M2a6fK[26]
CSS containment: https://goo.gle/396F080[27]
Content visibility: https://goo.gle/3yAFcqC[28]
Priority hints: https://goo.gle/3M4O388[29]
CSS size-adjust: https://goo.gle/3w3E25G[30]
SIMD: https://goo.gle/3Fy57Rj[31]
Interaction to next paint: https://goo.gle/3NaAyUF[32]
CHIPS: https://goo.gle/3wpBEFk[33]
Topics API: https://goo.gle/3ytEsDL[34]
UA client hints: https://goo.gle/3L1Pov4[35]
Webauthn: https://goo.gle/3wlrGVc[36]
Webauthn passkeys: https://goo.gle/3M6MuGA[37]
Media session API: https://goo.gle/3FwwbAC[38]
Window controls overlay: https://goo.gle/3L3xbNM[39]
Navigation API: https://goo.gle/3KVQGru[40]
Page transition API: https://goo.gle/3kVBFLF[41]
Themes in manifests: https://goo.gle/3ynnhUu[42]
Eyedropper API: https://goo.gle/3w1spfk[43]
Virtual keyboard API: https://goo.gle/3yx0CVI[44]
structuredClone: https://goo.gle/3M2DQc9[45]
createImageBitmap: https://goo.gle/38jXyBI[46]
Top level await: https://goo.gle/39VD8zz[47]
Private fields: https://goo.gle/3ytxPBi[48]
array.at: https://goo.gle/3yrenoU[49]
SharedArrayBuffer: https://goo.gle/3L3J2vb[50]
URLPattern: https://goo.gle/3P5e2y2[51]
Web codecs API: https://goo.gle/3Pnl2GS[52]
CSS cascade layers: https://goo.gle/3M58Ubc[53]
CSS :has(): https://goo.gle/38cggeF[54]
CSS container queries: https://goo.gle/3FA9Odx[55]
Container query polyfill: https://goo.gle/3ytfjJy[56]
参考资料
[1]
Ant Deisign : https://ant.design/components/checkbox-cn/
[2]
window.opener: https://developer.mozilla.org/docs/Web/API/Window/opener
[3]
contain: https://developer.mozilla.org/zh-CN/docs/Web/CSS/contain
[4]
MDN: https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Containment
[5]
contain: https://developer.mozilla.org/zh-CN/docs/Web/CSS/contain
[6]
counters: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Lists_and_Counters/Using_CSS_counters
[7]
quotes: https://developer.mozilla.org/en-US/docs/Web/CSS/quotes
[8]
DEMO页面: https://blogs.igalia.com/mrego/files/2019/01/css-contain-example.html
[9]
CSS Containment: https://drafts.csswg.org/css-contain/#content-visibility
[10]
介绍文章: https://web.dev/content-visibility/
[11]
《Web Authentication: 一个用于访问公钥凭证的API》: https://www.w3.org/TR/webauthn/
[12]
https://webauthn.io/: https://webauthn.io/
[13]
幽灵漏洞: https://zh.wikipedia.org/wiki/幽灵漏洞
[14]
Cascading and Inheritance Level5: https://www.mybj123.com/gohref.php?url=https://www.w3.org/TR/css-cascade-5/#at-layer
[15]
:scope: https://developer.mozilla.org/zh-CN/docs/Web/CSS/:scope
[16]
https://www.youtube.com/watch?v=5b4YcLB4DVI: https://www.youtube.com/watch?v=5b4YcLB4DVI
[17]
https://goo.gle/399xjOz: https://goo.gle/399xjOz
[18]
https://goo.gle/3N1kpRe: https://goo.gle/3N1kpRe
[19]
https://goo.gle/3L07LjR: https://goo.gle/3L07LjR
[20]
https://goo.gle/3M5jVt8: https://goo.gle/3M5jVt8
[21]
https://goo.gle/3wftMGh: https://goo.gle/3wftMGh
[22]
https://goo.gle/3ysFCzj: https://goo.gle/3ysFCzj
[23]
https://goo.gle/3L2zeS3: https://goo.gle/3L2zeS3
[24]
https://goo.gle/39SJgbJ: https://goo.gle/39SJgbJ
[25]
https://goo.gle/3w3DigU: https://goo.gle/3w3DigU
[26]
https://goo.gle/3M2a6fK: https://goo.gle/3M2a6fK
[27]
https://goo.gle/396F080: https://goo.gle/396F080
[28]
https://goo.gle/3yAFcqC: https://goo.gle/3yAFcqC
[29]
https://goo.gle/3M4O388: https://goo.gle/3M4O388
[30]
https://goo.gle/3w3E25G: https://goo.gle/3w3E25G
[31]
https://goo.gle/3Fy57Rj: https://goo.gle/3Fy57Rj
[32]
https://goo.gle/3NaAyUF: https://goo.gle/3NaAyUF
[33]
https://goo.gle/3wpBEFk: https://goo.gle/3wpBEFk
[34]
https://goo.gle/3ytEsDL: https://goo.gle/3ytEsDL
[35]
https://goo.gle/3L1Pov4: https://goo.gle/3L1Pov4
[36]
https://goo.gle/3wlrGVc: https://goo.gle/3wlrGVc
[37]
https://goo.gle/3M6MuGA: https://goo.gle/3M6MuGA
[38]
https://goo.gle/3FwwbAC: https://goo.gle/3FwwbAC
[39]
https://goo.gle/3L3xbNM: https://goo.gle/3L3xbNM
[40]
https://goo.gle/3KVQGru: https://goo.gle/3KVQGru
[41]
https://goo.gle/3kVBFLF: https://goo.gle/3kVBFLF
[42]
https://goo.gle/3ynnhUu: https://goo.gle/3ynnhUu
[43]
https://goo.gle/3w1spfk: https://goo.gle/3w1spfk
[44]
https://goo.gle/3yx0CVI: https://goo.gle/3yx0CVI
[45]
https://goo.gle/3M2DQc9: https://goo.gle/3M2DQc9
[46]
https://goo.gle/38jXyBI: https://goo.gle/38jXyBI
[47]
https://goo.gle/39VD8zz: https://goo.gle/39VD8zz
[48]
https://goo.gle/3ytxPBi: https://goo.gle/3ytxPBi
[49]
https://goo.gle/3yrenoU: https://goo.gle/3yrenoU
[50]
https://goo.gle/3L3J2vb: https://goo.gle/3L3J2vb
[51]
https://goo.gle/3P5e2y2: https://goo.gle/3P5e2y2
[52]
https://goo.gle/3Pnl2GS: https://goo.gle/3Pnl2GS
[53]
https://goo.gle/3M58Ubc: https://goo.gle/3M58Ubc
[54]
https://goo.gle/38cggeF: https://goo.gle/38cggeF
[55]
https://goo.gle/3FA9Odx: https://goo.gle/3FA9Odx
[56]
https://goo.gle/3ytfjJy: https://goo.gle/3ytfjJy
还没有任何评论,你来说两句吧