大文件Markdown渲染逻辑
下面我给你把10 万行 Markdown(每行是一张图片 URL)在网页中展示时,浏览器与服务器之间的真实图片请求逻辑讲得非常清楚,分 有预览 / 无预览 两种情况,并解释虚拟列表、懒加载、浏览器缓存、滚动触发机制等实际工作流。
⸻
✅ 一、核心原则:浏览器只会对进入视口或即将进入视口的资源发起请求
浏览器不会因为你加载了整个 MD 文本,就把里面所有图片一口气全部请求。
真正控制图片请求的是:
• 是否渲染了 <img>
• 是否使用 loading="lazy"
• 是否用了虚拟列表(Virtual Scroll)
• 缓存策略 (HTTP Cache)
• 是否有预览模式(例如缩略图 vs 原图)
⸻
🧨 二、无预览——直接显示图片(最常见)
🧯场景:10 万行 Markdown,每行 <img src="xxx">
浏览器不会一次性请求这 10 万张图
流程如下:
✔ 1. 首屏:进入视口的 <img> 才会发起请求
一般视口同时能显示几十张图片(取决于图片高度)。
👉 只有这几十张图片的网络请求会真正发出。
⸻
✔ 2. 有 loading="lazy" 的懒加载
默认现代浏览器给 Markdown 渲染出的 <img> 会自动懒加载:
<img src="xxx.jpg" loading="lazy">懒加载逻辑是:
状态 是否请求图片?
图片进入视口 请求
图片接近视口(约 300~800px) 预请求
图片离视口很远 不会请求
⸻
✔ 3. 下翻滚动时产生新请求
你往下翻时,浏览器会:
• 销毁或移除上方已经滚出视口的 DOM(如果用了虚拟列表)
• 渲染新进入视口的 <img>
• 对这些 <img> 触发请求
❗不会重复请求(命中缓存)
如果图片 URL 不变:
• memory cache(页面存在)立即加载,不走网络
• disk cache(刷新后)走 disk,不走远程服务器
• 只有第一次加载才访问服务器
⸻
✔ 4. 即使 10 万行,浏览器最多请求几十张
总结一句话:
图片会随着你的滚动逐步请求,不会一次性下载全部。
⸻
🖼 三、有预览(缩略图)模式
如果你页面的逻辑是:
列表 预览图(小图)
点击后 加载大图(原图)
则请求逻辑变成两级:
⸻
✔ 1. 列表滚动时:只加载小图(thumbnail)
例如:
![]()
滚动时只会加载缩略图——通常几十 KB。
⸻
✔ 2. 用户点开预览:再请求原图
例如:
https://img.example.com/full/123.jpg
只有在用户点击预览时才发起大图请求。
⸻
效果:极大减少流量 & 加载压力
⸻
🧱 四、虚拟列表(Virtual Scroll)的情况
如果你使用:
• React Window
• Vue Virtual List
• React Virtualized
• 或自写虚拟滚动组件
那逻辑会更极端:
⸻
✔ 只渲染屏幕中 + 上下 buffer 区的几十行
真正的 DOM 中大概只有:
50~200 行图片节点
其余 99,800 行根本不渲染,也不会发出任何请求。
这让 10 万行页面毫无压力。
⸻
🔍 五、总结表:有预览 / 无预览 的请求行为
情况 滚动时是否请求图片? 最大请求数量 是否重复请求?
无预览、直接 <img> ✔ 是 屏幕能容纳的图数量(几十个) ❌ (缓存)
有预览小图 ✔ 是,但只请求缩略图 几十个 ❌
点击预览原图 ✔ 请求大图 1 ❌
使用虚拟列表 ✔ ~50–200 ❌
不使用懒加载 ❗可能加载更多(视图开始渲染时) 可能上千 一样缓存
⸻
🧠 六、你滚到 10 万行最底部,总共请求了多少?
✔ 理论上:
~(视口可见数量 + buffer)× 2 遍(上下滚)
比如:
• 可见 30 行
• buffer 100 行
你完整滚一遍,只会加载:
约 200 × 2 = 400 张图片左右
而不是 100,000 张。
⸻