有些你不知道是正常的……因为他们基本都没怎么被浏览器实现 🥶
CSS Toggles
https://tabatkins.github.io/css-toggle/
纯 CSS 实现状态切换一般使用 Checkbox 或者 Radio 配合选择器来实现。实现起来麻烦不说,而且 CheckBox/Radio 的位置限制了你可控制的范围,用起来很不方便。
交互中越来越多依赖状态,比如 Tab, 弹窗, Summary 等,所以有了原生的状态切换草案。目前还是草案中,不过已经有对应的 Polyfill 了 https://github.com/oddbird/css-toggles
toggle-root
:定义该元素可切换状态<toggle-root> = <custom-ident> [ <toggle-states> [at <toggle-value>]? || <toggle-overflow> || group || self ]? <toggle-states> = <integer [1,∞]> | '[' <custom-ident>{2,} ']' <toggle-value> = <integer [0,∞]> | <custom-ident> <toggle-overflow> = cycle | cycle-on | sticky // mode // mode 1 at 0 cycle wide // mode 3 at 0 // mode [light dark] at light
toggle-overflow
定义设置的值超出之后的行为,针对数字类型有效- cycle / cycle-on: 比最小值小则为最大值,比最大值大则为 0 / 1
- sticky: 最小值 <= value <= 最大值
self
定义触发元素和可切换元素的查找关系:- wide: 任意
- narrow: 必须是父子关系
<toggle-trigger> = <custom-ident> <trigger-action>? <trigger-action> = [prev | next] <integer [1,∞]>? | set <toggle-value> // mode // mode next 1 // mode prev 1 // mode next 2 // mode 2 // mode set light
toggle
:当可切换元素和切换触发器元素为同一个时,可使用toggle
进行简写
我们可以通过 Element.toggles()
来获取可切换元素的所有切换状态枚举,也通过 Element.addEventListener('togglechange', ...)
获取当前可切换元素的当前状态。
更多示例见:https://toggles.oddbird.net
PopUp API
https://open-ui.org/components/popup.research.explainer
如果要自己从 0 开始写一个弹窗是比较麻烦的,需要考虑很多事情:全屏浮层,内容居中,页面滚动失效,遮罩点击关闭,ESC 按下关闭…
所以就有好多组件封装,虽然原生已经有 <dialog> 标签可以干类似的事情了。但毕竟还是有点原始,所以 Chrome 就将 PopUp 原生实现了~~(真卷啊)~~。
popup
: auto | hint | manual 默认为 auto,指定多弹窗的关系- popuptoggletarget:指向带有
popup
属性的元素 id,用来切换 popup 元素的显隐 - popupshowtarget:指向带有
popup
属性的元素 id,用来显示 popup 元素 - popuphidetarget:指向带有
popup
属性的元素 id,用来隐藏 popup 元素 Element.showPopUp()
Element.hidePopUp()
目前仅最新的 Chromium 生效 :-) 还有些问题~ https://chromestatus.com/feature/5463833265045504
CSS 作用域
https://www.w3.org/TR/css-scoping-1/
以往我们要实现 CSS 作用域,组件之间样式不互相影响,一般就是 BEM 命名或者 CSS Module, CSS in JS 之流最终生成带 hash 的唯一选择器伪实现,亦或是使用 Shadow DOM 这种高成本的完美实现。
现在我们能直接使用 @``scope
来实现样式隔离了!
不用太多介绍,简单好用~
structuredClone
https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
原生的深拷贝方法,可以对结构化数据进行深拷贝,避免 JSON.parse(JSON.stringify())
的尴尬。
structuredClone(value: any, { transfer?: any[] })
- 不允许克隆
Error
、Function
和DOM
对象,如果对象中含有,将抛出DATA_CLONE_ERR
异常。 - 不保留
RegExp
对象的lastIndex
字段。 - 不保留属性描述符,
setters
以及getters
(以及其他类似元数据的功能)。例如,如果一个对象用属性描述符标记为read-only
,它将会被复制为read-write
。 - 不保留原型链。
Navigation API
SPA 的基石路由管理,早有 History API 支持,但因为本质是历史记录的管理,缺少一些切换后的控制等功能,所以 Chrome 从新提出了 Navigation API 来专门实现路由管理。
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname === '/') {
navigateEvent.intercept({handler: loadIndexPage});
} else if (url.pathname === '/cats/') {
navigateEvent.intercept({handler: loadCatsPage});
}
});
navigateEvent
包含以下信息:
canIntercept
是否支持拦截,跨域等无法拦截场景会返回false
destination.url
跳转目标地址hashChange
是否是锚点跳转userInitiated
是否是由页面内<a>
标签触发的跳转,为false
表示是浏览器前进后退等触发的跳转downloadRequest
是否是由具有download
属性的链接带来的跳转formData
表单跳转时对应提交的表单数据,可以针对 Form 表单拦截后发送数据navigationType
枚举值"reload"
,"push"
,"replace"
或"traverse"
(类似history.goBack()
)之一。如果是"traverse"
,则无法通过preventDefault()
阻止跳转signal
提供给拦截 handler 中异步请求使用,方便当跳转终端后同步中断请求scroll()
控制跳转后滚动,在异步 handler 中比较有用,可能会多次滚动
function shouldNotIntercept(navigationEvent) {
return (
!navigationEvent.canIntercept ||
navigationEvent.hashChange ||
navigationEvent.downloadRequest ||
navigationEvent.formData
);
}
signal
和 scroll()
的例子:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContentURL = new URL(
'/get-article-content',
location.href
);
articleContentURL.searchParams.set('path', url.pathname);
const response = await fetch(articleContentURL, {
signal: navigateEvent.signal,
});
const articleContent = await response.json();
renderArticlePage(articleContent);
navigateEvent.scroll();
const secondaryContent = await getSecondaryContent(url.pathname);
addSecondaryContent(secondaryContent);
},
});
}
});
// navigate
const { committed, finished } = navigation.navigate('/articles/hello-world');
设置好跳转拦截回调后,我们就能正常使用 navigation.navigate()
进行跳转了。返回两个 Promise 对象,分别对应 Navigate 完成的状态 committed
,以及导航拦截的回调 Handler 结束的状态 finished
。
比起 History API 惊喜的是,我们可以通过 navigation.entries()
获取当前所有的历史记录。通过 navigation.currentEntry
返回当前的记录。
navigation.entries()
获取的只能是同域的历史记录,跨域的无法获取。
兼容性:https://caniuse.com/mdn-api_navigation_navigate
URLPattern
https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API
可以算是原生版的 path-to-regexp ,用来做 URL 格式匹配解析的。最开始是因为 service worker 场景有解析拦截的资源请求地址的需求创造,但适用所有 URL 解析场景。
虽然 URLPattern 可以解析完整的域名,但一般 hostname 相关可以用 URL 解析,query 可以使用 URLSearchParams 解析。所以其实用的比较多的场景还是 pathname 的解析。
const pattern = new URLPattern({ pathname: '/books/:id(\\d+)' });
console.log(pattern.test('https://example.com/books/123')); // true
console.log(pattern.exec('https://example.com/books/123').pathname.groups); // { id: '123' }
console.log(pattern.test('https://example.com/books/detail')); // false
- 使用
:<group>
来为当前匹配内容分组 {}
是非捕获组,相当于正则中的(?:)
,可以在后面增加{}?
表示可选,不加的话其实可有可无(正则表达式)
也可以通过正则进行精确匹配,可以跟在命名分组的后面,相当于(?<group>正则表达式)
。内部正则关键字需要做转义处理。*
表示贪婪匹配,独立使用相当于正则.*
,也可以搭配在前几个规则后使用,例如:id*
+
相当于{1,}
不可独立使用
使用 URLPattern 而不是自己使用正则解析的一个好处就是它会帮助我们把 URL 规范化之后再进行解析,而不是简单的做一个字符串的正则转换匹配。
目前仅 Chrome 系兼容性还行,Node 也暂时还没有跟上版本。不过有对应的 Polyfill 了已经。
图片
https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio
如果需要按比例显示图片,一般会保持比例设置 DOM 尺寸后使用 background-image
或者使用 <img>
来展示,比较不便。所以增加了 aspectio-ratio
属性直接支持设置图片显示的比例。
配合 object-fit
属性指定图片比例不对的时候填充模式,食用更加。
img {
aspect-ratio: 16 / 9;
object-fit: contain;
}
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading
object-fit
的兄弟属性object-position
:用于指定图片的展示区域https://developer.mozilla.org/en-US/docs/Web/CSS/object-position
为了性能优化我们一般都会为图片增加懒加载的支持,这个官方也做了原生的支持。
<img src="image.jpg" alt="..." loading="lazy">
https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images
为了更好的性能优化,我们一般会从图片尺寸和图片格式上对图片资源进行处理,此为响应式图片。
关于图片格式,静图有 jpg
,png
,webp
,avif
,jpeg XL
这些格式。动图有 gif
,apng
,webp
,avif
之这些格式。avif
是基于视频编码 AV1 衍生的图片格式。针对不同的浏览器适配不同的格式,我们可以使用 <picture>
进行图片渲染。
<picture>
<source type="image/avif" srcset="....avif" />
<img src="....webp" loading="lazy" />
</picture>
除了格式之外,我们还可以按照分辨率来设置,图片尺寸也不在话下。综合如下:
<style>
img {
width: 320px;
aspect-ratio: 320 / 240;
object-fit: contain;
}
</style>
<picture>
<source
type="image/avif"
srcset="...320x240.avif 1x ...640x480.avif 2x ...960x720.avif 3x"
/>
<img
srcset="...320x240.webp 1x ...640x480.webp 2x ...960x720.webp 3x"
src="....webp"
loading="lazy"
/>
</picture>
参考资料:
- The Future of CSS: CSS Toggles
- 有哪些以往需要使用javascript实现的功能现在可以直接使用html或者css实现? - 知乎
- Scope Proposal & Explainer
- Proposal for CSS @when | CSS-Tricks
- JS 深拷贝的原生终结者 structuredClone API - 掘金
- https://developer.chrome.com/docs/web-platform/navigation-api/
- URLPattern brings routing to the web platform
- https://web.dev/learn/design/picture-element/
下午好~
@Uyoahz: 晚上好 OwQ
@怡红公子: 晚上好
@Yumin Gui: 早上好~
早上好~
@kangkk: 哈哈哈哈,看到的时候已经是第二天的早上了,早上好呀~