惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Troy Hunt's Blog
Blog — PlanetScale
Blog — PlanetScale
Engineering at Meta
Engineering at Meta
F
Full Disclosure
Recorded Future
Recorded Future
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
博客园_首页
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
H
Hacker News: Front Page
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
Cloudbric
Cloudbric
PCI Perspectives
PCI Perspectives
有赞技术团队
有赞技术团队
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
TaoSecurity Blog
TaoSecurity Blog
L
Lohrmann on Cybersecurity
量子位
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tailwind CSS Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
B
Blog RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research
大猫的无限游戏
大猫的无限游戏
P
Proofpoint News Feed
N
News and Events Feed by Topic
罗磊的独立博客
T
Threat Research - Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
IT之家
IT之家
M
MIT News - Artificial intelligence
S
Security @ Cisco Blogs
O
OpenAI News
AI
AI
S
Securelist
Simon Willison's Weblog
Simon Willison's Weblog
The Last Watchdog
The Last Watchdog
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题

戴兜的小屋

Hi! 2024 - 戴兜的小屋 优雅地实现滚动容器遮罩 - 戴兜的小屋 [简中] Google Summer of Code & Chrome Extensions - 戴兜的小屋 Google Summer of Code & Chrome Extensions - 戴兜的小屋 SCSS+WindiCSS实现主题色切换 - 戴兜的小屋 Plaid CTF Writeup [Treasure Map/CSS] - 戴兜的小屋 写一个炫酷的个人名片页✨✨ - 戴兜的小屋 【戴兜的游戏安利】Hi-Fi RUSH,不可多得的动作爽游 - 戴兜的小屋 Manifest V3扩展Content Script绕过CSP限制点击页面内元素 - 戴兜的小屋 在 Nuxt.js 中配合 windicss 实现暗黑模式适配 - 戴兜的小屋 实现滚动时Header自动隐藏 - 戴兜的小屋 你好!2023 - 戴兜的小屋 新年快乐! - 戴兜的小屋 CSS Layout API初探:瀑布流布局实现 - 戴兜的小屋 你好! 2022 - 戴兜的小屋 手撸一个前端天气卡片 - 戴兜的小屋 Hi!2021 - 戴兜的小屋 服务器又搬迁啦! - 戴兜的小屋 base64数据隐写实现原理分析 - 戴兜的小屋
!important导致TransitionGroup失效 - 戴兜的小屋
戴兜文章作者 · 2022-11-23 · via 戴兜的小屋

大家如果曾经接触过 Vue, 那么大抵会对其自带的组件 TransitionGroup 有所了解。这篇文章便记录了 TransitionGroup 中「移动动画」的一些使用细节。

或许你对 TransitionGroup 的「移动动画」还不太了解,那么我在这里浅浅地介绍一下。正常使用时,你需要为 .[name]-move 类提供一个过渡样式,例如 transition: all 0.5s ease;,这样,当 TransitionGroup 内的元素位置变更后, Vue 会尝试让变动了位置的元素从老位置平滑过渡到新位置。当然,Vue 也支持新增元素和删除元素的过渡效果,只需要为 [name]-enter-from[name]-leave-to 类名提供样式,这不是本文的重点,故不再详细介绍。

曾经的我,也像大部分人那样按部就班把样式写完,没出过问题。

直到群里有人告诉我:「试试给元素增加一个常驻的、带有 !importanttransition 样式,会让过渡失效」

我当场愣住 😀 ,这在当时的我看来是一件很难理解的事情:本身过渡时 Vue 就会通过 [name]-move 为元素加上 transition 属性,为什么提前给元素加上一个优先级最高的 transition 属性,过渡反而没法生效了呢?

从源码入手

我们可以在 TransitionGroup.ts 阅读与 TransitionGroup 相关的代码内容

初始化阶段

不难发现,Vue 在渲染函数内,将子元素数组 children 赋值给 setup 函数作用域下的变量 prevChildren (L113)。同时,通过一个 for 循环遍历 prevChildren (L135),将每个子元素的位置信息储存于 positionMap 中(旧位置)。

该阶段中与我们讨论内容相关的,便是这两处内容。prevChildren 的赋值,使得 Vue 能够在之后的 updated 生命周期中,得以取得子元素引用,方便进行相关操作。而 positionMap,让 Vue 有能力在之后的操作中,得到元素的原始位置。

此处的 positionMap 是一个 WeakMap,Vue 使用元素对象作为 key 值,能够保证在元素被销毁后,positionMap 中对应元素的位置信息被适时自动回收。

Updated 生命周期

当 TransitionGroup 内的子元素发生变动后,会调用 onUpdated 注册的回调函数,同时也是在这里,Vue 完成了过渡所需要的大部分操作。

首先,Vue 通过一个 forEach,再次遍历获取了各个元素的位置信息,储存到 newPositionMap 中(L72)。此时,一个元素的新旧位置分别储存在 newPositionMappositionMap 中,我们需要做的,就是让元素从旧位置平滑过渡到新位置。

接下来就是关键所在:既然是 updated 生命周期,此时元素们应该已经在新位置呆着了,又谈何过渡?所以,我们要做的,并不是单纯让元素从旧位置过渡到新位置。而是将已经位于新位置的元素,重新放回旧位置,再让其平滑返回到新位置,完成整个过渡过程。那么 Vue 是怎么完成的呢?

L73的代码是这样的:

const movedChildren = prevChildren.filter(applyTranslation)

Vue 使用 applyTranslation 方法过滤出了需要移动的子元素数组 movedChildren。通过观察函数和变量的命名,我们几乎可以肯定地说,在过滤的同时,Vue 还对元素进行了一些操作,而实际上也确实如此。

function applyTranslation(c: VNode): VNode | undefined {
    const oldPos = positionMap.get(c)!
    const newPos = newPositionMap.get(c)!
    const dx = oldPos.left - newPos.left
    const dy = oldPos.top - newPos.top
    if (dx || dy) {
        const s = (c.el as HTMLElement).style
        s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`
        s.transitionDuration = '0s'
        return c
    }
}

对于需要移动的元素,Vue 计算了新旧位置差值,使用 css 属性 Transform,将元素放回旧位置。特别关注一下 s.transitionDuration = '0s'之后要考

但是实际上,此时的元素仍然没有回到旧位置。浏览器会将样式变动加入渲染队列中,而不是立刻渲染。这里涉及浏览器重排(reflow)的相关知识,可以搜索相关文章来进行阅读。

为了保证元素立刻被放置到旧位置,在L73得到 movedPosition 后,Vue 执行了 forceReflow 方法(L76),强制触发重排。而 forceReflow 方法内容也很简单(Transition.ts#L461)

function forceReflow() {
  return document.body.offsetHeight
}

这里也是个前端小知识 😛 ,读取文档的 offsetHeightoffsetWidth,也能触发文档重排。

可以这么理解:渲染队列中存在改动而不进行重排直接获取文档宽度或高度,会导致拿到的元素宽高是过时的,所以浏览器在读取前对文档进行了重排。

之后的工作就很简单了,只需要给元素加上 [name]-move 类名(L81),然后去除之前添加的 transitionDurationTransform 属性,元素自然就能平滑返回到新位置啦~监听 transitionend 事件(L83-L93),做一些收尾工作(去除过渡相关类名等)

读到这里,我们已经能够解决文章开头的那个问题了。实现过渡效果,需要确保元素正位于旧位置。在 Vue 中,为了确保文档重排后元素通过 Transform 放到了旧位置,Vue 将元素的过渡时间设置为 0s 并进行了一次强制重排。但是人为添加的高优先级 transition 属性导致重排时元素没法第一时间回到旧位置,也就没有过渡效果了。

我也写了一个小 demo,简化了 TransitionGroup 中的无关代码,感兴趣可以看看 https://codepen.io/DaiDR/pen/VwdMRxa