跨文档视图转换:无人提及的那些陷阱

Lobsters Hottest 工具

摘要

一篇技术文章,解释CSS中跨文档视图转换的当前实现,涵盖已弃用的meta标签、常见陷阱(如4秒超时和宽高比变形)以及正确的选择加入方法。

<p><a href="https://lobste.rs/s/4hnsdc/cross_document_view_transitions_gotchas">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/18 16:32

# 跨文档视图过渡:没人提起的那些坑 | CSS-Tricks 来源:https://css-tricks.com/cross-document-view-transitions-part-1/ 我浪费了整整一个星期六。不是那种懒散的星期六,而是那种难得的、精打细算的、“我终于要搞出那个东西了”的星期六。我看过Jake Archibald的演示 (https://jakearchibald.com/2024/view-transitions-handling-aspect-ratio-changes/)。我看了Chrome Dev Summit的演讲。我知道跨文档视图过渡是真的,你可以在普通的旧式多页面站点 (https://css-tricks.com/native-like-animations-for-page-transitions-on-the-web/)上获得那些流畅的原生感页面过渡,而不需要任何框架。没有React。没有Astro。没有那种把你的多页面应用(MPA)假装成单页面应用(SPA)的客户端路由器。就是HTML页面互相链接,浏览器在它们之间处理动画。太好了。 所以我开始构建。然后,啥都没用。 我找到的第一个教程让我把``放到我的``里。看起来很简单。我把它加到了两个页面上,点击了我的链接,然后……什么都没发生。没有过渡。没有错误。就是一个正常的、瞬间的页面加载,就像回到了2004年。我打开DevTools,再三检查语法,重启服务器,尝试Chrome Canary,清除缓存。什么都没用。我做了任何一个有自尊心的开发者在这种时候都会做的事——我从博客文章里逐字复制代码并粘贴进去。还是没用。 我花了两个小时确信自己是个白痴。 结果发现那个``标签的语法?**已废弃。** (https://css-tricks.com/snippets/css/basic-view-transition/)没了。Chrome曾经支持它,然后换成了基于CSS的选择加入机制,互联网上有一半的教程仍然展示着旧的方式。那些老博客文章排名依然很高。它们看起来很有权威性。但现在它们就是错的。不是作者写得不好而错——而是规范在大家脚下移动了,没有人回去更新自己的文章。 我找到的另一半教程则是在讲同文档视图过渡。SPA相关的那种。用JavaScript调用`document.startViewTransition()`,在你自行切换DOM内容的时候用,这很酷也很有用,但当你真正坐下来实现它时,这完全是另一个功能。API接口不同。思维模型不同。那些坑*非常*不同。然而,去搜索“view transitions tutorial”,祝你好运,看你能不能在翻到第三段之前搞清楚你读的是哪种变体。 所以,如果你在这里,我猜你经历过类似的事情。你试了meta标签。没用。你试了JavaScript API,在一个真正的多页面站点上,然后发现它只在一个文档内触发。你也许在一个演示中搞了个半成品,但一旦你加入真实内容它就崩了——图片拉伸得奇形怪状,过渡挂起好几秒没个说法,或者你的CSS文件变成了200行`view-transition-name` (https://css-tricks.com/almanac/properties/v/view-transition-name/)声明,因为你有一个40个产品卡片的网格。你责怪了自己。那不是你的错。这个功能周围的文档生态系统现在一团糟,而且规范一直在变。 **这是两部分系列文章的第一部分**,它就是我那个星期六希望存在的文章。我们将涵盖当前正确的选择加入方式,即用CSS中的`@view-transition` (https://css-tricks.com/almanac/rules/v/view-transition/)(不是meta标签,不是JavaScript),然后深入探讨那个会在慢速页面上静默杀死你过渡的4秒超时及其调试方法,接着修复在每张图片丰富的过渡中看起来都像哈哈镜的宽高比扭曲问题,最后正确掌握`pagereveal`和`pageswap`事件,以便对整个生命周期进行编程控制。 在第二部分,我们将解决规模化问题——如何处理数十或数百个元素上的`view-transition-name`,而不会让你的样式表变成灾难;`view-transition-name`和`view-transition-class` (https://css-tricks.com/almanac/properties/v/view-transition-class/)的区别;即时命名模式;以及以正确的方式处理`prefers-reduced-motion` (https://css-tricks.com/almanac/rules/m/media/prefers-reduced-motion/)。 #### 跨文档视图过渡系列 1. **没人提起的那些坑 (https://css-tricks.com/cross-document-view-transitions-part-1/)** *(你在这里!)* 2. **跨越数百个元素的视图过渡规模化** *(下周一!)* 去冲杯咖啡吧。也许续一杯。这篇内容很密集,我不会浪费你的时间,但需要覆盖的东西很多,而且没有哪件事是显而易见的。 ## 旧方法已死 `` `` `` /* 这是当前的选择加入方式 - 放在你的CSS里 */ @view-transition { navigation: auto; } `` 这是最小化设置。两个HTML文件,每个一个CSS规则。注意,截至2026年,基于Chromium的浏览器和Safari 18.2+支持跨文档视图过渡。Firefox的支持正在开发中,在我写这篇文章的时候。 就这些。两个HTML文件。每个一个CSS规则。在支持的浏览器(如现代Chromium或Safari 18.2+)中,点击它们之间的链接,你会得到一个平滑的交叉淡入淡出效果。没有JavaScript。没有meta标签。没有构建步骤。浏览器会截取旧页面的快照,截取新页面的快照,然后自动在它们之间进行动画。 现在,为什么规范从meta标签移到了CSS at-rule?这不是随意的。meta标签是一个钝器。它对于整个页面要么开要么关。你不能说“在桌面上启用过渡,但在低端硬件上感觉卡卡的手机上不启用”。你不能根据用户偏好有条件地选择加入。它就在那里,要么有,要么没有。CSS方法开启了所有这些可能性: `` /* 只有用户没有要求减少动效时才启用过渡 */ @media (prefers-reduced-motion: no-preference) { @view-transition { navigation: auto; } } `` `` /* 只在足够宽的视口上启用,保证动画感觉良好 */ @media (min-width: 768px) { @view-transition { navigation: auto; } } `` 这是一个真正的升级。你获得了与你已经拥有的每个其他CSS功能相同的条件能力。媒体查询、`@supports` (https://css-tricks.com/almanac/rules/s/supports/)、任何你想要的逻辑作用域——都能完美工作,因为选择加入机制存在于你的样式所在之处。 还有一个重要的细微差别:旧页面和新页面的CSS规则可以不同。两个页面都需要选择加入,过渡才会触发。如果页面A有`@view-transition { navigation: auto; }`,但页面B没有,你不会得到任何过渡。这实际上很有用——这意味着你的404页面或登录重定向可以在没有任何JavaScript协调的情况下跳过过渡。 这里还有一点值得注意:`navigation: auto`只对用户发起的、同源的导航生效。如果用户点击一个普通链接或按下浏览器的“后退”按钮,你会得到一个过渡。但通过`window.location.href = "/somewhere"`编程设置,或者跨源链接,或者带有`POST`的表单提交?没有过渡。浏览器在何时触发方面故意保持保守,说实话,这是正确的选择。你不会想在处理支付创建的`POST`请求上看到一个花哨的交叉淡入淡出效果。 听着,如果你一直在按照过时的教程操作,并且你的过渡只是静默地不工作,那么几乎可以肯定就是这个原因。meta标签在Chrome 111中发布 (https://developer.chrome.com/blog/new-in-chrome-111?hl=en),经过几个月的实际使用,然后Chrome团队从Chrome 126左右开始 (https://developer.chrome.com/release-notes/126) 弃用了它,转而支持CSS at-rule。没有控制台警告。没有错误。旧的语法现在只是静默地什么都不做。老实说,DevTools里给个弃用警告本可以省去我(可能也省去你)很多痛苦,但事实就是这样。把meta标签换成CSS规则。这是第一步。这篇文章里的其他所有内容都建立在这个基础上。 ## 你的过渡会随机死掉,原因在此 `` // 把这个放到你的页面里,看看实际发生了什么 window.addEventListener("pagereveal", (event) => { if (!event.viewTransition) { console.log( "没有视图过渡 - 页面未选择加入或浏览器跳过了它", ); return; } // 这个能拯救你的理智 event.viewTransition.finished .then(() => console.log("过渡完成 ✅")) .catch((err) => { // 你会在这里看到 "TimeoutError",别处没有 console.error("过渡被杀死:", err.name, err.message); }); }); `` 这是没人会在他们博客文章里提到的事情:**跨文档视图过渡有一个强制的4秒超时。** 如果新页面在导航开始后的4秒内没有达到浏览器认为“可渲染”的状态,过渡就直接……死了。没有动画。没有交叉淡入淡出。新页面直接显示出来,就好像视图过渡不存在一样。而且,除非你连接好了那个`pagereveal`监听器并打开了控制台,否则你得不到任何迹象表明出了问题。 四秒钟听起来很宽裕——直到它不够用。想想真实网站上会发生什么。你的页面加载了。HTML到了,没问题,那很快。但你可能有一个很大的、会阻塞渲染的主图。可能有一个慢速的API调用,你的服务端在发送响应之前要等待它——比如一个产品页面要访问库存服务,一个仪表板在等待分析数据,任何在响应前实际做了工作的服务端渲染场景。也许你的连接不错,但页面从Google Fonts加载了三种网页字体,并且设置了`font-display: block`。其中任何一个都可能把你推到那个4秒窗口之外,超时机制不在乎*为什么*你慢。它直接砍掉过渡。 真正让人抓狂的部分?它在localhost上完美运行。你的开发服务器在80ms内响应。过渡流畅丝滑。你部署到生产环境,你的服务器正在冷启动一个lambda函数,或者你的CDN缓存未命中,突然之间,用户在第一次点击时得到了零个过渡。你在本地无法复现。你开始怀疑一切。 `` // 你也可以在旧页面上使用 `pageswap` 来捕获这个 // 对于清理或记录哪些导航失败很有用 window.addEventListener("pageswap", (event) => { if (event.viewTransition) { event.viewTransition.finished.catch((err) => { // 记录下来,发送到你的分析系统,随便什么 console.warn("传出过渡中止:", err.name); }); } }); `` 那么,你实际上该怎么处理它呢? **选项一:让你的页面更快。** 我知道,石破天惊的建议。但说真的——如果你的跨文档过渡死掉了,那说明你的页面加载确实很慢。超时就像一个性能警报。看看DevTools的Performance面板,运行一次Lighthouse审计(这可能并不完美 (https://www.smashingmagazine.com/2024/11/why-optimizing-lighthouse-score-not-enough-fast-website/)),找出是什么阻塞了首次渲染。这不是视图过渡特有的建议,但超时迫使你去关心它。 **选项二更有趣一些**,也是我希望自己早点知道的东西。 `` `` 这个: `` `` ……告诉浏览器:“不要在DOM中存在匹配`#hero`的元素之前,认为这个页面是可渲染的。”这听起来像是会让事情*更慢*,某种程度上也确实如此——它会延迟首次绘制。但对于视图过渡来说,这正是你想要的:你在告诉浏览器,在重要内容实际存在之前,先hold住快照,而不是拍摄一个半加载页面的截图,或者更糟,因为页脚的某个图片还在下载并阻塞了某些东西而超时。 这是一个权衡。你选择了一个略微延迟但流畅的过渡,而不是一个快速但已损坏的过渡。 老实说,从浏览器的角度来看,4秒的限制可能是个正确的决定。你不想让用户点击一个链接后,盯着一个冻结的页面看10秒钟,就为了浏览器等待做一个花哨的动画。在某个点上,直接显示该死的页面比一个漂亮的过渡更好。但我希望Chrome能更显眼地暴露这个超时——一个DevTools警告,一个性能标记,*任何东西*。现在它静默失败,这整件事情就是这么个问题。 还有一点值得知道:超时时钟从导航开始时开始计时,而不是从新页面的HTML开始到达时开始。网络延迟算在内。Time to First Byte (TTFB) 核心网页指标算在内。如果服务器需要2秒响应,页面之后需要2.5秒渲染,那么你就超了,即使每一部分单独来看都不觉得慢。 一个不止一次救过我的调试技巧:Chrome的DevTools有一个Animations面板(如果你没看到,它在“更多工具”下面),它可以实际捕获正在运行的视图过渡。你可以把它们减速到10%的速度,重播,并在动画过程中检查伪元素树。它对于视图过渡是否有效并不明显,但确实有效。结合上面的`pagereveal`监听器,你可以很快诊断出大多数超时问题。尽早放入那个`pagereveal`监听器。测试时注意你的控制台。你以后会感谢自己的。 ## 为什么你的图片看起来像太妃糖 这个用同文档演示先展示比较容易(因为你可以在一个文件里实际运行它),但问题及其修复对于跨文档过渡是完全一样的。 运行它。点击图片。看着那只狗变成愚蠢的橡皮泥。图片本身在两侧都有`object-fit: cover`。缩略图看起来很好,大图看起来也很好。但是在过渡期间呢?浏览器不会过渡你的``元素。它截取旧状态的*截图*,截取新状态的截图,然后在它们之间变形。那些截图是平坦的栅格图像。你精心应用的`object-fit` (https://css-tricks.com/almanac/properties/o/object-fit/)?没了。浏览器只是将一个位图从一个盒子大小缩放到另一个,当一个150x150的方块被拉伸成一个600x300的矩形时,你就得到了太妃糖。 这是修复方法: `` /* 修复方法 - 直接针对过渡伪元素 */ ::view-transition-old(hero-img), ::view-transition-new(hero-img) { /* 将快照视为容器中的图片 - 裁剪,不拉伸 */ object-fit: cover; overflow: hidden; } `` 就这么简单。两个属性,两个伪元素。 实际发生的事情:浏览器为每个命名的过渡生成一个伪元素树。对于一个具有`view-transition-name: hero-img`的元素,在动画期间你会得到这个结构: `` ::view-transition └── ::view-transition-group(hero-img) ├── ::view-transition-old(hero-img) └── ::view-transition-new(hero-img) `` `::view-transition-group` 平滑地将它的宽度和高度从旧尺寸动画到新尺寸。这就是你看到的正在变形的矩形。在它内部,`old` 和 `new` 伪元素持有实际的位图快照,默认情况下它们被设置为 `object-fit: fill` —— 意思是“拉伸以填充你所在的任何盒子,宽高比见鬼去”。切换到 `object-fit: cover` 告诉那些快照保持它们的宽高比并裁剪溢出的部分。和带有 `background-size: cover` 的背景图片是同样的思维模型。过渡仍然将盒子从正方形动画到矩形(或者任何你的形状),但内部的图片通过裁剪优雅地处理,而不是变形。 你也可以在这里使用 `object-fit: contain`,如果你更想看到带有信箱式黑边的完整图片而不是裁剪的话。这取决于什么对你的内容看起来合适。但90%的情况下你会想要 `cover`,特别是对于产品图片和主图。 对于跨文档过渡,CSS 是完全相同的——你只需把它放在两个页面的样式表中: `` /* 这可以跨文档工作。相同的选择器,相同的修复。 */ /* 把它放在你的全局样式里 */ ::view-transition-old(root), ::view-transition-new(root) { /* 默认情况下,根元素在过渡期间也会拉伸它的快照。 */ /* 通常你不需要这个,但如果你看到整个页面变形,加上它。 */ /* object-fit: cover; */ /* overflow: hidden; */ } ::view-transition-old(hero-img), ::view-transition-new(hero-img) { object-fit: cover; overflow: hidden; } `` 等等,`root` 那个是怎么回事?当你设置 `@view-transition { navigation: auto; }` 而没有手动命名任何元素时,默认的过渡是在名为 `root` 的伪元素上进行的。那个伪元素包含了整个页面的截图。如果你的页面布局在两个状态之间改变了尺寸(比如不同的布局宽度,或者导航栏折叠/展开),整个页面的截图也会被拉伸。通常你不会注意到,因为背景是纯色或者某种渐变,它会自然地交叉淡入淡出。但如果你有跨越整个宽度的固定元素或者边界清晰的布局,你可能会看到整个视口在过渡期间被挤压或拉伸。修复方法是一样的——在 `::view-transition-old(root)` 和 `::view-transition-new(root)` 上设置 `object-fit: cover` 或 `object-fit: contain`。 一个专业提示:`view-transition-name` 的值必须匹配。如果旧页面有一个 `view-transition-name: hero-img` 的元素,而新页面上有某种对应物,它也需要有 `view-transition-name: hero-img`。名称不必全局唯一,但它们在页面之间需要一致才能匹配。如果你命名了旧页面上的元素但没有在新页面上命名,浏览器就会将它视为旧状态中新出现的元素,并且不会尝试平滑过渡它——它只会淡入/淡出。这在某些情况下没问题,但对于共享内容元素,匹配名称是关键。 对于产品图片、头像、文章缩略图——基本上任何在两个页面状态间改变尺寸的图片——把这个 CSS 放进去训练成肌肉记忆。它是解决视图过渡中大约 70% 古怪视觉行为的法宝。 ## 生命周期事件让你的脚本能控制过渡 到此为止,我们只用了 CSS。没有执行任何 JavaScript 来让过渡运行。但如果你想做一些比交叉淡入淡出更复杂的事情——比如根据内容延迟过渡,或记录有多少过渡失败,或仅为特定导航类型(前进 vs. 后退)自定义动画——你需要 `pagereveal` 和 `pageswap` 事件。 **`pageswap` 事件在旧页面上触发,紧接在浏览器截取旧页面快照之后,但在显示新页面之前。** 你在新页面加载之前有一个最后的钩子。这通常用于:保存滚动位置;触发分析事件以记录用户离开;或者根据用户导航到何处以及如何导航来调整旧页面上的过渡。 **`pagereveal` 事件在新页面上触发,在浏览器截取新页面快照之后,但在动画实际播放之前。** 这是你用来确保新页面为过渡做好准备的事件。也是我们之前用来捕获超时错误的事件。 这两个事件都在它们各自的 `event.viewTransition` 属性上暴露了 `ViewTransition` 对象。那就是你可以附加 `.ready`、`.finished`、`.updateCallback` 和 `.skipTransition()` 的对象。是的,`updateCallback`——同文档 API 的核心部分——在跨文档上下文中也有效。区别在于你不会用它来交换 DOM。DOM 已经交换好了(毕竟你加载了一个新页面)。你用它来运行一次性计算,这些计算*在新页面渲染后*发生,但在*过渡快照被拍摄之前*。这对于诸如根据新页面的内容延迟过渡之类的事情非常有用,如果你不想完全依赖 4 秒超时的话。 这里有一个重要的心理模型:对于跨文档过渡,`window` 对象在两个页面之间变化。旧页面上的 JavaScript 无法访问新页面的 DOM,反之亦然。这与 SPA 风格的同文档过渡有根本性的不同,在后者中,你处于同一个 JavaScript 执行上下文中,并且可以直接访问新旧状态。在跨文档情况下,你的事件监听器只能访问它们被触发时所在页面的作用域。`pageswap` 监听器运行在旧页面上。`pagereveal` 监听器运行在新页面上。没有共用变量。没有可传递的共享状态。 那么,你*可以*做什么呢? **记录性能。** 两个事件都允许你访问过渡何时准备好、何时完成或被中止。这可以让你建立真实用户监测(RUM)数据,了解你的过渡行为如何。在旧页面上使用 `pageswap`,你可以知道导航何时开始(因为 `performance.now()` 标记)。在新页面上使用 `pagereveal`,你可以知道过渡何时完成。从这两个时间戳,你可以计算出“过渡持续时间”指标,并在你的分析工具中追踪它。 **根据导航类型自定义动画。** `pagereveal` 事件有一个 `event.navigationType` 属性(如果支持),告诉你这是否是“前进”、“后退”或“重新加载”导航。你可以使用它来调整你的 CSS 动画。例如: `` window.addEventListener("pagereveal", (event) => { if (event.navigationType === "back") { document.documentElement.classList.add("reverse-transition"); } }); `` `` /* CSS */ .reverse-transition::view-transition-old(root) { animation-duration: 0.3s; animation-direction: reverse; } `` 同样的方法也适用于 `pageswap`,但要从旧页面的角度出发。 **为新页面上的延迟内容做好准备。** 这是 `updateCallback` 真正有用的地方。`ViewTransition.ready` Promise 在浏览器即将开始过渡动画时解析。但假设你知道你的新页面需要额外的时间——也许一个大的交互式图表正在渲染,或者你需要延迟过渡,直到目标部分滚动到视图中。你可以使用 `pagereveal` 来调整新页面的状态,就在快照被拍摄之前。你不会用它来改变整个布局,但你可以,例如,确保一个特定的元素具有适当的大小,或者运行一个快速计算来设置过渡参数。 这是一个真实的场景:你有一个博客网站,点击文章标题会转到带有目录、大标题图片和文章正文的文章详情页。在旧页面上,文章标题在卡片内。在新页面上,相同的标题被放大并使用 `<h1>`。你想用视图过渡平滑地过渡标题。但也许新页面也有一些 lazyloaded 的图片,这些图片需要时间才能出现。过渡在页面可渲染后立即开始,但那些 lazyloaded 的图片可能还没准备好。 你可以做什么:告诉浏览器等待一个特定的元素,就像我们之前用渲染阻塞做的 ```` 一样。但也许你不能那样做,因为你使用的是通用的 CMS 模板。在这种情况下,最干净的解决方法是保持你的过渡简单并让它交叉淡入淡出,或者,如果延迟对体验至关重要,使用 `viewTransition.ready.then(...)` 等待,然后触发 `updateCallback`,这会强制浏览器重新截图,为你提供一个更准确的过渡,代价是延迟。 实际上,对于大多数跨文档网站,我建议不要过于复杂地设置 `updateCallback`。让默认的交叉淡入淡出做它该做的事,为你的内容级元素添加 `view-transition-name` 以防止长条状变形,然后收工。过度设计过渡是导致漏洞百出和沮丧的一条捷径。 ## 总结一下跨文档视图过渡的实际操作清单 首先,忘记 meta 标签。现在是 2026 年(或更晚)了。Chrome 和 Safari 支持跨文档视图过渡,但它们通过 CSS `@view-transition` 指令实现。添加这个: `` @view-transition { navigation: auto; } `` 把它放在你的全局样式表中。它告诉浏览器:“嘿,对于这个页面上的用户发起的同源导航,请使用视图过渡。”双方页面都需要选择加入。没有 JavaScript。没有构建步骤。只有 CSS。 其次,预先解决超时问题。交通灯是你的朋友。使用 `pagereveal` 事件来捕获超时错误,以便在开发过程中你确切知道是否存在问题: `` window.addEventListener("pagereveal", (event) => { if (event.viewTransition) { event.viewTransition.finished.catch((err) => { console.warn("过渡超时或中止:", err.name); }); } }); `` 如果过渡持续超时,优化你的页面渲染性能或使用 ```` 告诉浏览器等待关键内容就绪。 第三,修复图片宽高比变形。你的漂亮图片在过渡期间看起来会像融化了一样,因为浏览器正在缩放位图快照。修复方法是针对 `view-transition-name` 伪元素并应用 `object-fit`: `` ::view-transition-old(your-name), ::view-transition-new(your-name) { object-fit: cover; overflow: hidden; } `` 对于根元素 `root`,如果你看到容器变形,也可以这样做。 第四,根据使用 `pageswap` 和 `pagereveal` 事件来实现自定义逻辑和监控。这些事件让你能够连接到过渡的生命周期,记录性能,并为动画添加特定于导航的样式。 第五,如果它是一个静态生成的网站或简单的多页面应用程序,可能不需要更多了。**视图过渡之于导航,就像 CSS 之于样式。** 声明式。由浏览器处理。只有在每个页面的上下文中才有意义。这就是它的美妙之处。每次构建一个复杂的 `updateCallback` 或试图在没有框架的情况下模仿 SPA 路由器的行为之前,问自己:“这个过渡真的*需要*那样工作吗,还是默认的交叉淡入淡出加上适当的命名元素就足够好了?”通常答案是后者。 现在,去修复你的视图过渡,从所有那些仍然引用 meta 标签的废弃教程中拯救你的星期六。

相似文章

我如何在博客中使用CSS View Transitions

Lobsters Hottest

作者详细介绍了如何在静态博客上仅使用CSS跨文档视图过渡来为页面导航添加动画,无需JavaScript路由,包括设置布局列和动画。

CSS:不可避免的坏部分

Lobsters Hottest

一位非Web开发者的个人博客文章讨论了CSS中不可避免的坏部分,包括布局难题、浏览器默认设置和过度使用包装器,同时强调了可处理简单任务的子集。

论渲染差异

Hacker News Top

博客文章宣布推出CodeView,这是一个虚拟化优先的React组件,可高效渲染大型代码差异,归属于六个月前发布的Diffs库。

Tw-fade: 纯CSS滚动驱动的边缘遮罩

Hacker News Top

Tw-fade 是一个新的 Tailwind CSS 插件,用于纯 CSS 滚动驱动的边缘淡出效果,无需 JavaScript 即可实现优雅的淡出效果。