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

推荐订阅源

云风的 BLOG
云风的 BLOG
Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recent Announcements
Recent Announcements
B
Blog RSS Feed
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
I
InfoQ
L
LangChain Blog
U
Unit 42
Google DeepMind News
Google DeepMind News
I
Intezer
T
Tenable Blog
C
Check Point Blog
N
News and Events Feed by Topic
Project Zero
Project Zero
The Register - Security
The Register - Security
博客园 - Franky
Application and Cybersecurity Blog
Application and Cybersecurity Blog
B
Blog
Forbes - Security
Forbes - Security
Security Archives - TechRepublic
Security Archives - TechRepublic
The Cloudflare Blog
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Vercel News
Vercel News
N
Netflix TechBlog - Medium
博客园 - 三生石上(FineUI控件)
腾讯CDC
T
Tailwind CSS Blog
SecWiki News
SecWiki News
S
Secure Thoughts
C
Cisco Blogs
M
MIT News - Artificial intelligence
V
Vulnerabilities – Threatpost
小众软件
小众软件
S
Security @ Cisco Blogs
罗磊的独立博客
F
Full Disclosure
aimingoo的专栏
aimingoo的专栏
Google Online Security Blog
Google Online Security Blog
T
Threat Research - Cisco Blogs
博客园 - 聂微东
P
Privacy International News Feed
V
Visual Studio Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
C
Cyber Attacks, Cyber Crime and Cyber Security
Hacker News: Ask HN
Hacker News: Ask HN

Zhullyb's Blog

竹林里有冰的博客 Nuxt SSG 博客的尾斜杠到底怎么加? | 竹林里有冰的博客 小米 Xiaomi Book Pro 14 (Ultra X7) Linux 兼容性实测 | 竹林里有冰的博客 国内(大陆)版小米 FCM 熄屏断连:Rootless 环境下的尝试与可能的解决方案 | 竹林里有冰的博客 我没法访问 dl.google.com —— 记一次 TUN 下的网络 debug | 竹林里有冰的博客 Vercel 的缓存控制,你注意过吗? | 竹林里有冰的博客 小记 —— Caddy 在 Layer 4 上的流量代理实践 | 竹林里有冰的博客 你的域名后缀拖慢你的网站速度了嘛?——再谈 DNS 冷启动 | 竹林里有冰的博客 DNS 冷启动:小型站点的“西西弗斯之石” | 竹林里有冰的博客 HTTP/2 Server Push 已事实性“死亡”,我很怀念它 | 竹林里有冰的博客 Nuxt Content v3 中数组字段的筛选困境与性能优化 | 竹林里有冰的博客 后 OCSP 时代,浏览器如何应对证书吊销新挑战 | 竹林里有冰的博客 初试 Github Action Self-hosted Runner,想说爱你不容易 | 竹林里有冰的博客 DNS 解析延迟毁了我的图床优化 | 竹林里有冰的博客 Vue Markdown 渲染优化实战(下):告别 DOM 操作,拥抱 AST 与函数式渲染 | 竹林里有冰的博客 Vue Markdown 渲染优化实战(上):从暴力刷新、分块更新到 Morphdom 的华丽变身 | 竹林里有冰的博客 node-sass 迁移至 dart-sass 踩坑实录 | 竹林里有冰的博客 2025 年,如何为 web 页面上展示的视频选择合适的压缩算法? | 竹林里有冰的博客 el-image 和 el-table 怎么就打架了?Stacking Context 是什么? | 竹林里有冰的博客 2025年,前端如何使用 JS 将文本复制到剪切板? | 竹林里有冰的博客 ssh 拯救世界——通过 ssh 隧道在内网服务器执行 APT 更新 | 竹林里有冰的博客 Cudy TR3000 吃鹅(daed)记 | 竹林里有冰的博客 使用 Cloudflare Workers 监控 Fedora Copr 构建状态 | 竹林里有冰的博客
前端中的量子力学——一打开 F12 就消失的 Bug | 竹林里有冰的博客
竹林里有冰 · 2025-06-08 · via Zhullyb's Blog

前端「量子态」现象的首次观测#

这事说来也邪乎,半个月前吃着火锅唱着歌,在工位上嘎嘎写码,发现一个诡异的 bug。作为如假包换的人类程序员,写出 bug 是再正常不过的事情了,但这 bug 邪门就邪门在我一打开 F12 的 DevTools 观察相关的 dom 结构,这 bug 就自动消失了;再把 DevTools 一关,Ctrl + F5 一刷新页面,Bug 又出现了。

下面是使用 iframe 引入的 demo

“观测”指南“观测”指南

这 Bug 给我整得脑瓜子嗡嗡的,我又不是物理学家,写个前端怎么量子力学的观察者效应都给我整出来了(?

观测者效应(Observer effect),是指“观测”这种行为对被观测对象造成一定影响的效应。

在量子力学实验中,如果要测算一个电子所处的速度,就要用两个光子隔一段时间去撞击这个电子,但第一个光子就已经把这个电子撞飞了,便改变了电子的原有速度,我们便无法测出真正准确的速度(不确定原理)。时间流逝的快慢也会受到观测者的影响,用很高的频率去观测粒子的衰变,反而使得粒子长时间不衰变。

——wikipedia

量子迷雾❌浏览器机制✅#

这里先稍微解释一下 demo 中的代码片段:

if (scrollIndex >= groupLength) {
  setTimeout(() => {
    wrapper.style.transition = "none";
    scrollIndex = 0;
    wrapper.style.transform = `translateY(-${crollIndex * itemHeight}px)`;

    requestAnimationFrame(() => {
      wrapper.style.transition = "transform 0.5s cubic-bezier(0.25, 0.1, 0.25, 1)";
    });
  }, 500);
}

我这边拿到的需求是需要写一个无限滚动的轮播标题列表,每次展示三个,2 秒后标题列表整体上移,原本的第一个标题就移出可视范围了,下面会新补充一个新的标题列表。(可能解释的不清楚,但各位应该都看过上面的 demo 了)

当列表滚动到最底部的时候,我先取消 transition 过渡动画效果,趁机将整体列表平移到上一次可视范围内出现相同的三个标题的位置,再把 transition 过渡动画的效果加回来,这样就能在视觉上造成无限滚动的效果。

但问题就出在明明把 transition 属性取消了,但这一次平移仍然触发了过渡动画效果。

说实话,这是我短暂的码农生涯当中最绝望的一次,一方面是遇到的 bug 过于逆天以至于说出去都可能没人信,一方面是遇上这种问题是完全不能指望搜索引擎能给出什么解决方案的——毕竟我自己都不知道该怎么组织关键词进行搜索。

这是小麦茶,带我入坑前端的学长这是小麦茶,带我入坑前端的学长

于是抱着试一试的心态,把相关代码喂给 ChatGPT-4o 看看能不能问出个所以然来。

你描述的现象——“滚动第九次时列表出现突兀的自下而上跳动,而打开 F12 时不会出现问题”——几乎可以确定是由于浏览器在某些渲染状态下跳过了某些帧(帧率波动)或者定时器精度的问题导致动画突变

这种问题多半发生在“使用 setInterval 控制动画”和“切换样式(transition)时机不当”所引起的 过渡跳帧问题,而打开 DevTools 会 强制刷新帧或提高定时器精度,从而掩盖了这个问题

太好了,是 requestAnimationFrame,我们有救了#

window.requestAnimationFrame() 方法会告诉浏览器你希望执行一个动画。它要求浏览器在下一次重绘之前,调用用户提供的回调函数。

——MDN

这是 GPT 给出的方案,非常有效

if (scrollIndex >= groupLength) {
  setTimeout(() => {
    wrapper.style.transition = "none";
    scrollIndex = 0;
    wrapper.style.transform = `translateY(-${crollIndex * itemHeight}px)`;

    requestAnimationFrame(() => {
+      requestAnimationFrame(() => {
         wrapper.style.transition = "transform 0.5s cubic-bezier(0.25, 0.1, 0.25, 1)";
+      });
    });
  }, 500);
}

如果觉得嵌套两层 requestAnimationFrame 比较难理解,那下面的代码是等效的

if (scrollIndex >= groupLength) {
  setTimeout(() => {
    scrollIndex = 0;

    requestAnimationFrame(() => {
      // 第一帧
      wrapper.style.transition = "none";
      wrapper.style.transform = `translateY(-${crollIndex * itemHeight}px)`;
      // 第二帧
      requestAnimationFrame(() => {
        wrapper.style.transition = "transform 0.5s cubic-bezier(0.25, 0.1, 0.25, 1)";
      });
    });
  }, 500);
}

总之,我们需要杜绝浏览器将设置 transform 偏移值(瞬移列表位置)与恢复 transition 动画两件事合并到同一帧里去,而两层嵌套的 requestAnimationFrame 方法能很好的解决这个问题

驯服量子态:前端开发者的新技能#

就这样,通过使用两层requestAnimationFrame,我们成功驯服了这个"量子态"的bug。现在无论是否打开F12,它都会乖乖地按照我们的预期滚动,不再玩消失的把戏。

看来,在前端的世界里,我们不仅要懂JavaScript,还得懂点量子力学。下次再遇到这种"一观测就消失"的bug,不妨试试这个"量子纠缠解决方案"——双重requestAnimationFrame,没准就能让bug从"量子态"坍缩成"稳定态"呢!

当然,如果你有更神奇的 debug 经历,欢迎分享你的经历——毕竟,在代码的宇宙里,我们永远不知道下一个bug会以怎样的形态出现。也许,这就是编程的乐趣(?)所在吧!

本文由 ChatGPT 与 DeepSeek 协助撰写,但 bug 是真人真事(泪)。

参见#