【免费下载链接】textfold
Fold papers, web pages, files, and AI replies into five levels of detail. Zoom from an overview to the original text.
TextFold 是一个把论文、网页、文件和 AI 回复折叠成五个详细程度级别的阅读工具,支持从主题概览一路放大到原文。很多读者第一次体验时都会问同一个问题:用滚轮、双指捏合或缩放条去"缩放"一篇文档,为什么文字会平滑地滑动、缩放、淡入淡出,而不是生硬地重新排版?
这篇文章带你拆解TextFold 平滑缩放背后的三层实现:用FLIP 动画完成"逐词变形"、用关键帧预布局让缩放过程零布局开销,再用zoombench从屏幕帧级实测平滑度,作为每次改动的回归门槛。
一、五个级别:缩放是一个"位置",不是一次"跳转" 🎚️
TextFold 把任意文档折成五档:
| 级别 | 名称 | 你看到什么 |
|---|---|---|
| 4 | Full | 原文,每个字 |
| 3 | Brief | 作者的 1–3 句原话 |
| 2 | Key | 每个要点的结论和理由 |
| 1 | Takeaway | 每个章节的一句话 |
| 0 | Topic | 全文变成章节主题地图 |
在实现上,滚轮、捏合、滑条并不是"跳到下一个级别",而是移动一个连续的缩放位置(4 = Full … 0 = Topic)。你可以停在半路、随时折返;手一停,位置就会弹性地吸附到最近的级别。这一点在 textfold/web/reader.js 开头的注释里写得很直白:zoom as a position, not a jump。
二、FLIP 动画:逐词变形的原理 🔍
先说结论:TextFold 阅读器里,每一个可见的部分——词、公式、圆点、标题、图——都是一个带data-id的元素,这个 id 在所有级别之间保持不变。级别切换时,它做的事情就是标准的 FLIP(First, Last, Invert, Play):
- First:切换前,对屏幕附近的元素拍一张"快照"——记录每个部分的位置和外观。快照只量视口附近的东西(一篇完整论文有约 2 万个这样的小块),并且按"容器 + 类名"缓存计算样式,避免反复触发样式计算,见 snapshot();
- Last:把内容重新渲染成新级别;
- Invert:对每个"幸存"的部分,算出它从旧位置到新位置、从旧大小到新大小的差值;
- Play:用 Web Animations API 给每个元素挂一个
transform: translate() scale()的动画,让它滑回自己的新位置,消失的部分则由一个"幽灵副本"在原地淡出。
核心动画构建在 buildMotion(),一次完整的定时变形由 morph() 串起来。FLIP 整段代码就在 textfold/web/reader.js 里。
下面这段动图就是 FLIP 的效果:级别切换时,词和公式沿着连续的路径滑动、缩放,而不是整页刷新:
三、关键帧预布局:一个"不做任何布局"的舞台 🎬
FLIP 解决的是"切换那一瞬间"的动画,但还有一个更棘手的问题:每跨越一个级别,屏幕附近的内容就要重新排版一次(普通论文上约 30–70 ms)。这个停顿,手放在滚轮和滑条上能明显感觉到卡顿。
TextFold 的解法叫 "the stage"(舞台),源码注释在 reader.js,核心思想非常巧妙:
- 空闲时预布局:当读者停下来不动时,程序利用每一个空闲瞬间,在一个隐藏的sandbox(沙盒)里,把当前可见片段在每一个级别各排版一次,并把每个可见部分的"定位副本"存下来——一共5 张关键帧。预热的调度逻辑在 warm(),一次只排一个级别,绝不抢占交互。
- 相邻级别预建"配对":两张关键帧之间,每个部分要么整体平移缩放(move)、要么淡出(out)、要么淡入(in)、要么换字体时交叉淡化(x)。把运动方式相同的几百个词合并成一组,整组只有一个 GPU 图层、一条动画——密集论文从"几千个词各跑各的"变成"几百组一起动"。配对构建在 buildPair(),注释里还留着一个真实教训:不分组时,某篇论文一半的帧都被丢掉了。
- 缩放时只放片,不排版:缩放一触发,页面被舞台盖住(页面保持原样、不再重绘),每一帧都只是移动和淡入淡出副本——包括跨越级别的那几帧,全程零布局。舞台的准入在 stageBegin(),逐帧推进在 stageFrame()。
- 静止时换回真页面:缩放停在某个级别时,页面按该级别关键帧的样子画出真身,在同一帧内接管舞台的位置,观感上完全无缝,见 stageCommit()。
两个工程细节值得一提:沙盒和舞台都放在一个封闭的 shadow tree里——几千个副本的增删移不在文档里,浏览器插件(翻译器、广告拦截)就无须处理它们,实测证明留在页面里会让页面"冻结数秒";而当关键帧还没就绪(新开的页面、跨度过大的跳跃)时,缩放会回退到第二节说的实时 FLIP 动画,保证任何时候都有得放。
四、zoombench:以屏幕视角的帧级实测 📈
"感觉流畅"不是指标。TextFold 把平滑度当规则——项目文档 docs/DEVELOPMENT.md 里有一节专门叫Smoothness is a rule:任何改动都不许让缩放变得更不流畅,改之前和改之后都必须实测。
测量工具是 tools/zoombench.py。它为什么这么设计,文件开头的注释讲得很清楚:
- 用 Playwright 驱动 Chromium,开 Mac 的 GPU(Metal)、2 倍像素密度,模拟真实读者的屏幕;
- 在每个版本页面上重放同样的手势:捏合放大、捏合缩小、拖拽级别条、点击 Full;
- 然后读Chrome 自己的帧 trace:实际呈现的帧数、丢帧数、最长的帧间隔(统计逻辑见 stats())。
为什么不用requestAnimationFrame量帧间隔?因为默认无头浏览器是软件渲染、1 倍像素,只看得见主线程——v0.8 版本在那个环境里"每一帧都在 17 ms 以内、完美无缺",却在真实 GPU 2 倍屏上每 5 帧丢 1 帧,而后者才是读者真正感觉到的。
运行方式(在密集论文 2512.23916 上对比各版本):
python3 -m textfold export 2512.23916 python3 tools/zoombench.py 2512.23916输出类似:pinch out ->L3: {'ms': 1760, 'presented': 105, 'dropped': 0, 'drop%': 0, 'worst gaps ms': [18, 17, 17, 17]}——丢帧数和最长卡顿一目了然。
五、小结:三层机制各管一段
- FLIP 动画:级别离散跳转时的逐词变形,靠
data-id贯穿五个级别,量旧位、排新版、反转变形、滑回原位; - 关键帧预布局 + 舞台:连续缩放时的零布局体验,空闲时把五个级别全部预排好,缩放时只移动 GPU 上的副本;
- zoombench 帧级实测:用真实 GPU、真实屏幕帧 trace 把"平滑"变成一个可回归的硬指标。
想继续深挖,入口是 textfold/web/reader.js(渲染 + FLIP 变形 + 舞台)和 tools/zoombench.py(平滑度实测);项目文档在 docs/DEVELOPMENT.md 的Smoothness is a rule一节。
【免费下载链接】textfold
Fold papers, web pages, files, and AI replies into five levels of detail. Zoom from an overview to the original text.
相关推荐
CartCut关键帧动画完全指南:19个内置动效预设,不装After Effects做出平滑运动特效
CartCut关键帧动画完全指南:19个内置动效预设,不装After Effects做出平滑运动特效 CartCut 是一款面向 AI 智能体的开源视频编辑器
Mergen开发环境搭建:从编译到运行的简单步骤
Mergen开发环境搭建:从编译到运行的简单步骤 Mergen是一款功能强大的x64 PE到LLVM IR反混淆与反虚拟化工具,能够将汇编代码转换为LLVM中间
深度解析开源无人机飞控系统:从STM32实战到嵌入式系统开发
深度解析开源无人机飞控系统:从STM32实战到嵌入式系统开发 在嵌入式系统开发领域,无人机飞控系统代表着实时控制技术的最高挑战。本文将深入探讨基于STM32的轻
无人机嵌入式智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考