来分享下 鹅厂前端开发工程师 @jesonliao 总结的当前前端界的些新知识。
本文介绍一些 Web 平台的新技术。这些技术不只是尝鲜,而是已经达到可用阶段,用到生产实践中去可以切实提升开发效率和用户体验。本文内容主要选取自 2022 Google I/O 大会的视频 What's new for the web platform。
目录
- accent-color
- datetime-local
- Back/forward cache
- loading="lazy"
- aspect-ratio
- CSS Containment
- content-visibility
- Priority Hints
- size-adjust
- WebAuthn
- structuredClone
- Top level await
- Private fileds
- array.at()
- SharedArrayBuffer
- CSS cascade layers
- CSS :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后,优化效果非常明显
页面在进入 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 协议的硬件设备。
可以在这里示例网站 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,RegExporArrayBuffer,遇到这些类型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));
// -> 12
console.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-origin
Cross-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: www.youtube.com/watch?v=5b4…[16]
-
CSS accent-color: goo.gle/399xjOz[17]
-
CSS color-scheme: goo.gle/3N1kpRe[18]
-
dialog: goo.gle/3L07LjR[19]
-
selectmenu: goo.gle/3M5jVt8[20]
-
Selectmenu demos: goo.gle/3wftMGh[21]
-
Input datetime-local: goo.gle/3ysFCzj[22]
-
COLRv1 fonts: goo.gle/3L2zeS3[23]
-
Back/forward cache: goo.gle/39SJgbJ[24]
-
loading="lazy": goo.gle/3w3DigU[25]
-
CSS aspect-ratio: goo.gle/3M2a6fK[26]
-
CSS containment: goo.gle/396F080[27]
-
Content visibility: goo.gle/3yAFcqC[28]
-
Priority hints: goo.gle/3M4O388[29]
-
CSS size-adjust: goo.gle/3w3E25G[30]
-
SIMD: goo.gle/3Fy57Rj[31]
-
Interaction to next paint: goo.gle/3NaAyUF[32]
-
CHIPS: goo.gle/3wpBEFk[33]
-
Topics API: goo.gle/3ytEsDL[34]
-
UA client hints: goo.gle/3L1Pov4[35]
-
Webauthn: goo.gle/3wlrGVc[36]
-
Webauthn passkeys: goo.gle/3M6MuGA[37]
-
Media session API: goo.gle/3FwwbAC[38]
-
Window controls overlay: goo.gle/3L3xbNM[39]
-
Navigation API: goo.gle/3KVQGru[40]
-
Page transition API: goo.gle/3kVBFLF[41]
-
Themes in manifests: goo.gle/3ynnhUu[42]
-
Eyedropper API: goo.gle/3w1spfk[43]
-
Virtual keyboard API: goo.gle/3yx0CVI[44]
-
structuredClone: goo.gle/3M2DQc9[45]
-
createImageBitmap: goo.gle/38jXyBI[46]
-
Top level await: goo.gle/39VD8zz[47]
-
Private fields: goo.gle/3ytxPBi[48]
-
array.at: goo.gle/3yrenoU[49]
-
SharedArrayBuffer: goo.gle/3L3J2vb[50]
-
URLPattern: goo.gle/3P5e2y2[51]
-
Web codecs API: goo.gle/3Pnl2GS[52]
-
CSS cascade layers: goo.gle/3M58Ubc[53]
-
CSS :has(): goo.gle/38cggeF[54]
-
CSS container queries: goo.gle/3FA9Odx[55]
-
Container query polyfill: goo.gle/3ytfjJy[56]
参考资料
[1]
Ant Deisign : ant.design/components/…
[2]
window.opener: developer.mozilla.org/docs/Web/AP…
[3]
contain: developer.mozilla.org/zh-CN/docs/…
[4]
MDN: developer.mozilla.org/zh-CN/docs/…
[5]
contain: developer.mozilla.org/zh-CN/docs/…
[6]
counters: developer.mozilla.org/en-US/docs/…
[7]
quotes: developer.mozilla.org/en-US/docs/…
[8]
DEMO页面: blogs.igalia.com/mrego/files…
[9]
CSS Containment: drafts.csswg.org/css-contain…
[10]
介绍文章: web.dev/content-vis…
[11]
《Web Authentication: 一个用于访问公钥凭证的API》: www.w3.org/TR/webauthn…
[12]
[13]
幽灵漏洞: zh.wikipedia.org/wiki/幽灵漏洞
[14]
Cascading and Inheritance Level5: www.mybj123.com/gohref.php?…
[15]
:scope: developer.mozilla.org/zh-CN/docs/…
[16]
www.youtube.com/watch?v=5b4… www.youtube.com/watch?v=5b4…
[17]
goo.gle/399xjOz: goo.gle/399xjOz
[18]
goo.gle/3N1kpRe: goo.gle/3N1kpRe
[19]
goo.gle/3L07LjR: goo.gle/3L07LjR
[20]
goo.gle/3M5jVt8: goo.gle/3M5jVt8
[21]
goo.gle/3wftMGh: goo.gle/3wftMGh
[22]
goo.gle/3ysFCzj: goo.gle/3ysFCzj
[23]
goo.gle/3L2zeS3: goo.gle/3L2zeS3
[24]
goo.gle/39SJgbJ: goo.gle/39SJgbJ
[25]
goo.gle/3w3DigU: goo.gle/3w3DigU
[26]
goo.gle/3M2a6fK: goo.gle/3M2a6fK
[27]
goo.gle/396F080: goo.gle/396F080
[28]
goo.gle/3yAFcqC: goo.gle/3yAFcqC
[29]
goo.gle/3M4O388: goo.gle/3M4O388
[30]
goo.gle/3w3E25G: goo.gle/3w3E25G
[31]
goo.gle/3Fy57Rj: goo.gle/3Fy57Rj
[32]
goo.gle/3NaAyUF: goo.gle/3NaAyUF
[33]
goo.gle/3wpBEFk: goo.gle/3wpBEFk
[34]
goo.gle/3ytEsDL: goo.gle/3ytEsDL
[35]
goo.gle/3L1Pov4: goo.gle/3L1Pov4
[36]
goo.gle/3wlrGVc: goo.gle/3wlrGVc
[37]
goo.gle/3M6MuGA: goo.gle/3M6MuGA
[38]
goo.gle/3FwwbAC: goo.gle/3FwwbAC
[39]
goo.gle/3L3xbNM: goo.gle/3L3xbNM
[40]
goo.gle/3KVQGru: goo.gle/3KVQGru
[41]
goo.gle/3kVBFLF: goo.gle/3kVBFLF
[42]
goo.gle/3ynnhUu: goo.gle/3ynnhUu
[43]
goo.gle/3w1spfk: goo.gle/3w1spfk
[44]
goo.gle/3yx0CVI: goo.gle/3yx0CVI
[45]
goo.gle/3M2DQc9: goo.gle/3M2DQc9
[46]
goo.gle/38jXyBI: goo.gle/38jXyBI
[47]
goo.gle/39VD8zz: goo.gle/39VD8zz
[48]
goo.gle/3ytxPBi: goo.gle/3ytxPBi
[49]
goo.gle/3yrenoU: goo.gle/3yrenoU
[50]
goo.gle/3L3J2vb: goo.gle/3L3J2vb
[51]
goo.gle/3P5e2y2: goo.gle/3P5e2y2
[52]
goo.gle/3Pnl2GS: goo.gle/3Pnl2GS
[53]
goo.gle/3M58Ubc: goo.gle/3M58Ubc
[54]
goo.gle/38cggeF: goo.gle/38cggeF
[55]
goo.gle/3FA9Odx: goo.gle/3FA9Odx
[56]