[{"data":1,"prerenderedAt":915},["ShallowReactive",2],{"blog-browser-rendering-pipeline":3},{"id":4,"title":5,"body":6,"category":900,"date":901,"description":902,"draft":903,"extension":904,"meta":905,"navigation":906,"path":907,"seo":908,"stem":909,"tags":910,"__hash__":914},"blog\u002Fblog\u002Fbrowser-rendering-pipeline.md","浏览器是如何渲染页面的？",{"type":7,"value":8,"toc":880},"minimark",[9,13,32,39,46,52,57,190,193,208,219,223,234,250,256,259,284,289,303,307,324,347,353,469,473,480,483,511,527,531,541,545,552,614,620,634,638,644,651,654,680,686,690,707,713,722,736,740,743,747,758,762,779,783,797,803,810,814,824,827,877],[10,11,5],"h1",{"id":12},"浏览器是如何渲染页面的",[14,15,16,17,21,22,25,26,31],"p",{},"当网络进程把 HTML 文档下载完成后，渲染进程会收到字节流，并往 ",[18,19,20],"strong",{},"渲染主线程"," 的消息队列里塞入一个 ",[18,23,24],{},"渲染任务","。主线程在 ",[27,28,30],"a",{"href":29},"\u002Fblog\u002Fjs-event-loop","事件循环"," 里取出该任务后，才开始「把文档变成屏幕上的像素」。",[14,33,34],{},[35,36],"img",{"alt":37,"src":38},"HTML 下载完成后渲染任务进入消息队列","\u002Fdemos\u002Fbrowser-rendering\u002Ftask-entry.svg",[14,40,41,42,45],{},"这不是一次性的函数调用，而是一条 ",[18,43,44],{},"流水线","：每个阶段有明确的输入和输出，上一阶段的产出是下一阶段的输入。可以把它想象成工厂里的装配线——材料从一端进去，成品从另一端出来。",[14,47,48],{},[35,49],{"alt":50,"src":51},"渲染流水线总览：主线程五段 + 合成三段","\u002Fdemos\u002Fbrowser-rendering\u002Fpipeline-overview.svg",[53,54,56],"h2",{"id":55},"总览八个阶段","总览：八个阶段",[58,59,60,79],"table",{},[61,62,63],"thead",{},[64,65,66,70,73,76],"tr",{},[67,68,69],"th",{},"阶段",[67,71,72],{},"执行者",[67,74,75],{},"输入",[67,77,78],{},"输出",[80,81,82,96,109,122,135,148,162,176],"tbody",{},[64,83,84,88,90,93],{},[85,86,87],"td",{},"HTML 解析",[85,89,20],{},[85,91,92],{},"HTML 字节流",[85,94,95],{},"DOM 树 + CSSOM 树",[64,97,98,101,103,106],{},[85,99,100],{},"样式计算",[85,102,20],{},[85,104,105],{},"DOM + CSSOM",[85,107,108],{},"带 Computed Style 的节点",[64,110,111,114,116,119],{},[85,112,113],{},"布局 Layout",[85,115,20],{},[85,117,118],{},"样式树",[85,120,121],{},"布局树（几何信息）",[64,123,124,127,129,132],{},[85,125,126],{},"分层 Layer",[85,128,20],{},[85,130,131],{},"布局树",[85,133,134],{},"图层树",[64,136,137,140,142,145],{},[85,138,139],{},"绘制 Paint",[85,141,20],{},[85,143,144],{},"图层",[85,146,147],{},"绘制指令集（display list）",[64,149,150,153,156,159],{},[85,151,152],{},"分块 Tiling",[85,154,155],{},"合成线程",[85,157,158],{},"绘制指令",[85,160,161],{},"图层小块",[64,163,164,167,170,173],{},[85,165,166],{},"光栅化 Raster",[85,168,169],{},"GPU 进程",[85,171,172],{},"块信息",[85,174,175],{},"位图（bitmap）",[64,177,178,181,184,187],{},[85,179,180],{},"画 Draw",[85,182,183],{},"合成线程 + GPU",[85,185,186],{},"位图",[85,188,189],{},"屏幕像素",[14,191,192],{},"记忆口诀：",[194,195,196,203],"ul",{},[197,198,199,202],"li",{},[18,200,201],{},"主线程","：Parse → Style → Layout → Layer → Paint",[197,204,205,207],{},[18,206,183],{},"：Tiling → Raster → Draw",[14,209,210,211,213,214,218],{},"主线程极其繁忙——",[27,212,30],{"href":29}," 里还要执行 JS、处理事件、跑计时器。渲染只是它众多职责之一；一旦某段 JS 长时间占用主线程，整条渲染流水线都会暂停（见 ",[27,215,217],{"href":216},"\u002Fblog\u002Freflow-repaint-transform","reflow、repaint 与 transform","）。",[53,220,222],{"id":221},"第一步html-解析","第一步：HTML 解析",[14,224,225,226,229,230,233],{},"解析 HTML 时，遇到 ",[18,227,228],{},"CSS 就解析 CSS","，遇到 ",[18,231,232],{},"JS 就执行 JS","。",[14,235,236,237,240,241,245,246,249],{},"为提高效率，浏览器在正式解析前还会启动 ",[18,238,239],{},"预解析（preload scanner）","，提前下载 HTML 里 ",[242,243,244],"code",{},"\u003Clink>","、",[242,247,248],{},"\u003Cscript>"," 引用的外部资源，减少主线程空等。",[14,251,252],{},[35,253],{"alt":254,"src":255},"解析 HTML 得到 DOM 树与 CSSOM 树","\u002Fdemos\u002Fbrowser-rendering\u002Fparse-dom-cssom.svg",[14,257,258],{},"解析完成后得到两棵树：",[194,260,261,267],{},[197,262,263,266],{},[18,264,265],{},"DOM 树","：文档结构，对应 HTML 标签的嵌套关系",[197,268,269,272,273,245,276,279,280,283],{},[18,270,271],{},"CSSOM 树","：汇总 ",[18,274,275],{},"用户代理默认样式",[242,277,278],{},"\u003Cstyle>","、外链 CSS、行内 ",[242,281,282],{},"style"," 等全部 CSS 规则",[285,286,288],"h3",{"id":287},"css-为什么不阻塞-html-解析","CSS 为什么不阻塞 HTML 解析？",[14,290,291,292,295,296,299,300,233],{},"主线程扫到 ",[242,293,294],{},"\u003Clink rel=\"stylesheet\">"," 时，即使 CSS 文件还没下载完，",[18,297,298],{},"也不会停下来等","，会继续解析后面的 HTML。CSS 的下载与解析由预解析等机制并行处理，因此 ",[18,301,302],{},"CSS 默认不阻塞 DOM 树的构建",[285,304,306],{"id":305},"js-为什么会阻塞-html-解析","JS 为什么会阻塞 HTML 解析？",[14,308,291,309,312,313,315,316,319,320,323],{},[18,310,311],{},"同步"," ",[242,314,248],{}," 时，会 ",[18,317,318],{},"暂停 HTML 解析","，先等 JS 下载并执行完。因为 JS 可能通过 ",[242,321,322],{},"document.write"," 等方式修改 DOM，必须保证 DOM 生成顺序与脚本执行顺序一致。",[14,325,326,245,329,245,332,335,336,339,340,342,343,346],{},[242,327,328],{},"defer",[242,330,331],{},"async",[242,333,334],{},"type=\"module\""," 等会改变具体行为，但 ",[18,337,338],{},"「同步脚本阻塞解析」"," 是默认规则，也是 ",[242,341,248],{}," 放 ",[242,344,345],{},"\u003C\u002Fbody>"," 前、或把脚本移到底部的原因。",[14,348,349],{},[35,350],{"alt":351,"src":352},"link 不阻塞解析，同步 script 会阻塞","\u002Fdemos\u002Fbrowser-rendering\u002Fcss-js-parse.svg",[354,355,358],"code-block",{"lang":356,"title":357},"html","同步脚本阻塞 DOM 构建",[359,360,364],"pre",{"className":361,"code":362,"language":356,"meta":363,"style":363},"language-html shiki shiki-themes github-light github-dark","\u003Cp id=\"before\">before\u003C\u002Fp>\n\u003Cscript>\n  document.querySelector('#before').textContent = 'changed'\n\u003C\u002Fscript>\n\u003Cp id=\"after\">after\u003C\u002Fp>\n\u003C!-- 解析器在遇到 script 时必须先执行完再继续 -->\n","",[242,365,366,397,407,431,441,462],{"__ignoreMap":363},[367,368,371,375,378,382,385,389,392,394],"span",{"class":369,"line":370},"line",1,[367,372,374],{"class":373},"sVt8B","\u003C",[367,376,14],{"class":377},"s9eBZ",[367,379,381],{"class":380},"sScJk"," id",[367,383,384],{"class":373},"=",[367,386,388],{"class":387},"sZZnC","\"before\"",[367,390,391],{"class":373},">before\u003C\u002F",[367,393,14],{"class":377},[367,395,396],{"class":373},">\n",[367,398,400,402,405],{"class":369,"line":399},2,[367,401,374],{"class":373},[367,403,404],{"class":377},"script",[367,406,396],{"class":373},[367,408,410,413,416,419,422,425,428],{"class":369,"line":409},3,[367,411,412],{"class":373},"  document.",[367,414,415],{"class":380},"querySelector",[367,417,418],{"class":373},"(",[367,420,421],{"class":387},"'#before'",[367,423,424],{"class":373},").textContent ",[367,426,384],{"class":427},"szBVR",[367,429,430],{"class":387}," 'changed'\n",[367,432,434,437,439],{"class":369,"line":433},4,[367,435,436],{"class":373},"\u003C\u002F",[367,438,404],{"class":377},[367,440,396],{"class":373},[367,442,444,446,448,450,452,455,458,460],{"class":369,"line":443},5,[367,445,374],{"class":373},[367,447,14],{"class":377},[367,449,381],{"class":380},[367,451,384],{"class":373},[367,453,454],{"class":387},"\"after\"",[367,456,457],{"class":373},">after\u003C\u002F",[367,459,14],{"class":377},[367,461,396],{"class":373},[367,463,465],{"class":369,"line":464},6,[367,466,468],{"class":467},"sJ8bj","\u003C!-- 解析器在遇到 script 时必须先执行完再继续 -->\n",[53,470,472],{"id":471},"第二步样式计算","第二步：样式计算",[14,474,475,476,479],{},"主线程遍历 DOM，结合 CSSOM，为每个节点算出最终用于渲染的样式，即 ",[18,477,478],{},"Computed Style","（计算样式）。",[14,481,482],{},"此阶段会把许多值「算实」：",[194,484,485,494,503],{},[197,486,487,490,491],{},[242,488,489],{},"red"," → ",[242,492,493],{},"rgb(255, 0, 0)",[197,495,496,490,499,502],{},[242,497,498],{},"em",[242,500,501],{},"px","（在父级字号已知时）",[197,504,505,506,510],{},"经过 ",[27,507,509],{"href":508},"\u002Fblog\u002Fcss-property-calculation","层叠、继承、默认值"," 得到每个属性的最终声明",[14,512,513,514,517,518,522,523,526],{},"但 ",[18,515,516],{},"百分比宽高"," 等仍可能待定——它们依赖 ",[27,519,521],{"href":520},"\u002Fblog\u002Fcss-containing-block","包含块","，要在 ",[18,524,525],{},"布局"," 阶段结合几何上下文才能定稿。",[53,528,530],{"id":529},"第三步布局layout","第三步：布局（Layout）",[14,532,533,534,537,538,233],{},"布局阶段遍历带样式的节点，计算 ",[18,535,536],{},"几何信息","：宽高、位置（相对包含块）、行盒高度等，产出 ",[18,539,540],{},"布局树（Layout Tree）",[285,542,544],{"id":543},"布局树-dom-树","布局树 ≠ DOM 树",[14,546,547,548,551],{},"两棵树 ",[18,549,550],{},"常常不是一一对应","：",[58,553,554,565],{},[61,555,556],{},[64,557,558,561,563],{},[67,559,560],{},"情况",[67,562,265],{},[67,564,131],{},[80,566,567,583,603],{},[64,568,569,574,577],{},[85,570,571],{},[242,572,573],{},"display: none",[85,575,576],{},"有节点",[85,578,579,580,131],{},"无几何信息，",[18,581,582],{},"不进",[64,584,585,594,597],{},[85,586,587,590,591],{},[242,588,589],{},"::before"," \u002F ",[242,592,593],{},"::after",[85,595,596],{},"无对应节点",[85,598,599,600,131],{},"有几何信息，",[18,601,602],{},"会进",[64,604,605,608,611],{},[85,606,607],{},"匿名行盒 \u002F 块盒",[85,609,610],{},"无独立节点",[85,612,613],{},"布局算法自动生成",[14,615,616],{},[35,617],{"alt":618,"src":619},"DOM 树与布局树并不总是一一对应","\u002Fdemos\u002Fbrowser-rendering\u002Fdom-vs-layout.svg",[14,621,622,623,626,627,630,631,233],{},"布局完成后，每个可见元素在屏幕上「占哪块地方」就确定了。改宽高、边距、",[242,624,625],{},"display"," 等会触发 ",[18,628,629],{},"reflow（回流）","，需要重新跑 Layout 及之后阶段——详见 ",[27,632,633],{"href":216},"reflow 与 repaint",[53,635,637],{"id":636},"第四步分层layer","第四步：分层（Layer）",[14,639,640,641,233],{},"主线程按策略把布局树 ",[18,642,643],{},"分成多个图层（Layer）",[14,645,646,647,650],{},"分层是性能优化：将来只改某一层的样式或内容时，可以 ",[18,648,649],{},"只更新该层"," 的后续步骤，而不必重算整页。",[14,652,653],{},"常见影响因素：",[194,655,656,659,666,674],{},[197,657,658],{},"滚动容器、滚动条",[197,660,661,662,665],{},"堆叠上下文（",[242,663,664],{},"z-index"," 等）",[197,667,668,245,671],{},[242,669,670],{},"transform",[242,672,673],{},"opacity",[197,675,676,679],{},[242,677,678],{},"will-change"," 提示浏览器提前分层",[14,681,682,683,233],{},"分层策略因浏览器而异，属于实现细节；开发上只需知道：",[18,684,685],{},"分层是为了增量更新更快",[53,687,689],{"id":688},"第五步绘制paint","第五步：绘制（Paint）",[14,691,692,693,696,697,699,700,245,703,706],{},"主线程为 ",[18,694,695],{},"每一层"," 单独生成 ",[18,698,147],{},"，描述这一层该怎么画——类似 Canvas 的 ",[242,701,702],{},"fillRect",[242,704,705],{},"drawText"," 等调用序列。",[14,708,709],{},[35,710],{"alt":711,"src":712},"图层与绘制指令","\u002Fdemos\u002Fbrowser-rendering\u002Flayers-paint.svg",[14,714,715,718,719,233],{},[18,716,717],{},"渲染主线程的工作到这里为止。"," 之后把各层的绘制信息交给 ",[18,720,721],{},"合成线程（Compositor Thread）",[14,723,724,725,245,728,731,732,735],{},"改 ",[242,726,727],{},"color",[242,729,730],{},"background"," 等可见但不改布局的样式，可能只需 ",[18,733,734],{},"repaint","（重算绘制及之后），不必完整 reflow。",[53,737,739],{"id":738},"第六八步分块光栅化上屏","第六～八步：分块、光栅化、上屏",[14,741,742],{},"合成线程接手后：",[285,744,746],{"id":745},"_1-分块tiling","1. 分块（Tiling）",[14,748,749,750,753,754,757],{},"把每一层划成更小的 ",[18,751,752],{},"图块（tile）","，交给 ",[18,755,756],{},"线程池"," 并行处理，避免单次任务过大。",[285,759,761],{"id":760},"_2-光栅化raster","2. 光栅化（Raster）",[14,763,764,765,767,768,770,771,774,775,778],{},"把块信息交给 ",[18,766,169],{},"，由 GPU 以极高速度转成 ",[18,769,175],{},"——每个块是一小块像素矩阵。靠近 ",[18,772,773],{},"视口"," 的块会 ",[18,776,777],{},"优先"," 光栅化，所以你滚动时远处内容可能稍晚才清晰。",[285,780,782],{"id":781},"_3-画draw","3. 画（Draw）",[14,784,785,786,789,790,792,793,796],{},"合成线程为每块位图生成 ",[18,787,788],{},"指引（quad）","：在屏幕上的位置、旋转、缩放等。",[242,791,670],{}," 的变形主要发生在此阶段，",[18,794,795],{},"不经过主线程的 Layout\u002FPaint","，因此动画往往更顺滑。",[14,798,799],{},[35,800],{"alt":801,"src":802},"分块、光栅化、quad、GPU 上屏","\u002Fdemos\u002Fbrowser-rendering\u002Fcompositor-gpu.svg",[14,804,805,806,809],{},"最后 GPU 进程通过系统调用把结果交给显卡。",[18,807,808],{},"渲染进程跑在沙盒里","，不能直接碰硬件，崩溃也不会拖垮整个系统。",[53,811,813],{"id":812},"常见面试答法精简版","常见面试答法（精简版）",[815,816,817],"blockquote",{},[14,818,819,820,823],{},"网络进程收到 HTML 后，产生渲染任务进入渲染主线程的消息队列；事件循环取出任务，开启流水线：",[18,821,822],{},"解析 → 样式 → 布局 → 分层 → 绘制 → 分块 → 光栅化 → 画","。主线程负责前五步，合成线程和 GPU 负责后三步。CSS 不阻塞 DOM 构建，同步 JS 会阻塞解析。改动样式可能触发 reflow\u002Frepaint，是流水线运行中的局部重跑。",[53,825,826],{"id":826},"小结",[194,828,829,836,841,850,865],{},[197,830,831,832,835],{},"渲染由 ",[18,833,834],{},"事件循环调度","，是主线程与合成线程协作的流水线。",[197,837,838,233],{},[18,839,840],{},"DOM + CSSOM → 样式 → 布局 → 图层 → 绘制指令 → 位图 → 屏幕",[197,842,843,846,847,233],{},[18,844,845],{},"同步 JS 阻塞解析","；",[18,848,849],{},"CSS 默认不阻塞 DOM",[197,851,852,855,856,855,859,846,862,864],{},[18,853,854],{},"Layout 定几何","，",[18,857,858],{},"Paint 出指令",[18,860,861],{},"Compositor + GPU 上屏",[242,863,670],{}," 多在合成阶段完成。",[197,866,867,868,245,871,245,873,876],{},"与 ",[27,869,870],{"href":508},"CSS 属性计算",[27,872,521],{"href":520},[27,874,875],{"href":216},"reflow\u002Frepaint"," 串联起来，就是前端性能优化的完整地图。",[282,878,879],{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":363,"searchDepth":409,"depth":409,"links":881},[882,883,887,888,891,892,893,898,899],{"id":55,"depth":399,"text":56},{"id":221,"depth":399,"text":222,"children":884},[885,886],{"id":287,"depth":409,"text":288},{"id":305,"depth":409,"text":306},{"id":471,"depth":399,"text":472},{"id":529,"depth":399,"text":530,"children":889},[890],{"id":543,"depth":409,"text":544},{"id":636,"depth":399,"text":637},{"id":688,"depth":399,"text":689},{"id":738,"depth":399,"text":739,"children":894},[895,896,897],{"id":745,"depth":409,"text":746},{"id":760,"depth":409,"text":761},{"id":781,"depth":409,"text":782},{"id":812,"depth":399,"text":813},{"id":826,"depth":399,"text":826},"frontend","2026-06-25","从 HTML 解析到 GPU 上屏，详解渲染主线程与合成线程分工，以及 DOM 树、布局树、图层与位图如何串联成流水线。",false,"md",{},true,"\u002Fblog\u002Fbrowser-rendering-pipeline",{"title":5,"description":902},"blog\u002Fbrowser-rendering-pipeline",[911,912,913],"browser","rendering","interview","4ob-xZgG6ctKPWs2Yp8VJvQBEfU9jF8aTlNCAjPdHBg",1782309644799]