[{"data":1,"prerenderedAt":4692},["ShallowReactive",2],{"category-frontend":3},[4,3237,4086,4345],{"id":5,"title":6,"body":7,"category":3223,"date":3224,"description":3225,"draft":3226,"extension":3227,"meta":3228,"navigation":463,"path":3229,"seo":3230,"stem":3231,"tags":3232,"__hash__":3236},"blog\u002Fblog\u002Freflow-repaint-transform.md","reflow、repaint 与 transform 为何高效",{"type":8,"value":9,"toc":3214},"minimark",[10,14,37,42,47,54,58,65,71,75,94,100,188,192,197,218,225,233,288,292,309,311,331,345,361,3180,3183,3211],[11,12,6],"h1",{"id":13},"reflowrepaint-与-transform-为何高效",[15,16,17,18,23,24,28,29,32,33,36],"p",{},"上一篇 ",[19,20,22],"a",{"href":21},"\u002Fblog\u002Fbrowser-rendering-pipeline","浏览器渲染流水线"," 讲的是「从零画一页」。页面跑起来之后，改 DOM 或样式会触发 ",[25,26,27],"strong",{},"局部重算","。面试里常说的 ",[25,30,31],{},"回流（reflow）","、",[25,34,35],{},"重绘（repaint）","，指的就是流水线里哪些阶段要重跑。",[38,39,41],"h2",{"id":40},"什么是-reflow回流","什么是 reflow（回流）？",[15,43,44],{},[25,45,46],{},"Reflow 的本质是重新计算布局树（Layout）。",[15,48,49,50,53],{},"凡是会影响布局树的操作——改宽高、边距、显示方式、插入删除节点等——都需要重新做 ",[25,51,52],{},"Layout"," 及之后的阶段（分层、绘制、分块、光栅化、合成）。",[55,56,57],"h3",{"id":57},"浏览器会合并多次改动",[15,59,60,61,64],{},"连续多次改布局相关属性时，浏览器往往 ",[25,62,63],{},"先合并","，等当前 JS 执行块结束后再统一算布局，避免同一帧里反复 reflow。",[15,66,67,68],{},"因此：",[25,69,70],{},"改属性触发的 reflow 常常是异步批量的。",[55,72,74],{"id":73},"读布局属性会强制同步-reflow","读布局属性会强制同步 reflow",[15,76,77,78,81,82,32,86,89,90,93],{},"如果 JS 在改完布局后又 ",[25,79,80],{},"读取"," ",[83,84,85],"code",{},"offsetWidth",[83,87,88],{},"getBoundingClientRect()"," 等依赖最新几何信息的属性，浏览器必须 ",[25,91,92],{},"立刻算出最新布局","，否则会返回过期数据。",[15,95,96,97],{},"所以常见性能建议：",[25,98,99],{},"先读后写，或批量改样式，避免读写交错。",[101,102,105],"code-block",{"lang":103,"title":104},"javascript","读写交错可能触发强制同步布局",[106,107,111],"pre",{"className":108,"code":109,"language":103,"meta":110,"style":110},"language-javascript shiki shiki-themes github-light github-dark","const el = document.querySelector('.box')\nel.style.width = '200px'\n\u002F\u002F 读宽度会迫使浏览器立即 reflow\nconsole.log(el.offsetWidth)\nel.style.width = '300px'\n","",[83,112,113,147,159,166,178],{"__ignoreMap":110},[114,115,118,122,126,129,133,137,140,144],"span",{"class":116,"line":117},"line",1,[114,119,121],{"class":120},"szBVR","const",[114,123,125],{"class":124},"sj4cs"," el",[114,127,128],{"class":120}," =",[114,130,132],{"class":131},"sVt8B"," document.",[114,134,136],{"class":135},"sScJk","querySelector",[114,138,139],{"class":131},"(",[114,141,143],{"class":142},"sZZnC","'.box'",[114,145,146],{"class":131},")\n",[114,148,150,153,156],{"class":116,"line":149},2,[114,151,152],{"class":131},"el.style.width ",[114,154,155],{"class":120},"=",[114,157,158],{"class":142}," '200px'\n",[114,160,162],{"class":116,"line":161},3,[114,163,165],{"class":164},"sJ8bj","\u002F\u002F 读宽度会迫使浏览器立即 reflow\n",[114,167,169,172,175],{"class":116,"line":168},4,[114,170,171],{"class":131},"console.",[114,173,174],{"class":135},"log",[114,176,177],{"class":131},"(el.offsetWidth)\n",[114,179,181,183,185],{"class":116,"line":180},5,[114,182,152],{"class":131},[114,184,155],{"class":120},[114,186,187],{"class":142}," '300px'\n",[38,189,191],{"id":190},"什么是-repaint重绘","什么是 repaint（重绘）？",[15,193,194],{},[25,195,196],{},"Repaint 的本质是重新生成绘制指令（Paint）及之后步骤。",[15,198,199,200,203,204,32,207,32,210,213,214,217],{},"改的是 ",[25,201,202],{},"可见样式但不改布局"," 时——例如 ",[83,205,206],{},"color",[83,208,209],{},"background",[83,211,212],{},"visibility","（在仍占位的情况下）——可能只需 ",[25,215,216],{},"Repaint","，不必完整 reflow。",[15,219,220,221,224],{},"但 ",[25,222,223],{},"布局信息本身也是可见结果的一部分","，所以：",[226,227,228],"blockquote",{},[15,229,230],{},[25,231,232],{},"Reflow 一定会引起 Repaint。",[234,235,236,252],"table",{},[237,238,239],"thead",{},[240,241,242,246,249],"tr",{},[243,244,245],"th",{},"操作类型",[243,247,248],{},"典型属性",[243,250,251],{},"至少触发的阶段",[253,254,255,275],"tbody",{},[240,256,257,261,272],{},[258,259,260],"td",{},"Reflow",[258,262,263,32,266,32,269],{},[83,264,265],{},"width",[83,267,268],{},"margin",[83,270,271],{},"display",[258,273,274],{},"Layout → … → Draw",[240,276,277,279,285],{},[258,278,216],{},[258,280,281,32,283],{},[83,282,206],{},[83,284,209],{},[258,286,287],{},"Paint → … → Draw",[38,289,291],{"id":290},"为什么-transform-效率高","为什么 transform 效率高？",[15,293,294,81,297,300,301,304,305,308],{},[83,295,296],{},"transform",[25,298,299],{},"不改变布局树","，也 ",[25,302,303],{},"不改变绘制指令的生成方式","（不触发典型意义上的 layout \u002F paint 重算）。它主要影响流水线最后的 ",[25,306,307],{},"Draw（合成）"," 阶段：在合成线程里对已有位图做位移、旋转、缩放。",[15,310,67],{},[312,313,314,324],"ul",{},[315,316,317,319,320,323],"li",{},[83,318,296],{}," 变化时，",[25,321,322],{},"渲染主线程"," 往往仍很空闲",[315,325,326,327,330],{},"主线程再忙，",[25,328,329],{},"也不应挡住"," 合成线程上的 transform 动画",[15,332,333,334,337,338,341,342,344],{},"对比：用 ",[83,335,336],{},"top"," \u002F ",[83,339,340],{},"left"," 做位移动画会改布局，通常走 ",[25,343,260],{}," 路径，代价大得多。",[15,346,347,350,351,354,355,357,358,360],{},[25,348,349],{},"注意："," 对比的前提是 ",[25,352,353],{},"画面一样、实现不同","。下面 demo 两个小球同步左右移动，左边用 ",[83,356,296],{},"，右边用 ",[83,359,340],{},"；视觉上应一致，差别在每一帧走合成还是 Layout。",[362,363,365],"code-playground",{"title":364},"transform vs left：同动画、不同实现",[106,366,370],{"className":367,"code":368,"language":369,"meta":110,"style":110},"language-vue shiki shiki-themes github-light github-dark","\u003Ctemplate>\n  \u003Cdiv class=\"wrap\">\n    \u003Cp class=\"tip\">\n      点「开始动画」：两球同步往复移动。左边 \u003Ccode>transform: translateX\u003C\u002Fcode>，右边\n      \u003Ccode>left\u003C\u002Fcode>。画面应一致；右轨每帧触发布局，主线程更忙时可能更卡。\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"compare\">\n      \u003Cdiv class=\"col\">\n        \u003Cdiv class=\"col-head\">\n          \u003Cspan class=\"tag good\">transform\u003C\u002Fspan>\n          \u003Cspan class=\"metric\">本帧 ≈ {{ transformMs }}ms\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"track\">\n          \u003Cdiv class=\"ball\" :style=\"{ transform: `translateX(${x}px)` }\" \u002F>\n        \u003C\u002Fdiv>\n        \u003Cp class=\"note\">合成阶段位移，不改布局树\u003C\u002Fp>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"col\">\n        \u003Cdiv class=\"col-head\">\n          \u003Cspan class=\"tag warn\">left\u003C\u002Fspan>\n          \u003Cspan class=\"metric warn-m\">{{ layoutMs }}ms\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"track\">\n          \u003Cdiv ref=\"leftBall\" class=\"ball\" :style=\"{ left: `${x}px` }\" \u002F>\n        \u003C\u002Fdiv>\n        \u003Cp class=\"note\">每帧改几何位置 → Layout\u003C\u002Fp>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"actions\">\n      \u003Cbutton class=\"btn\" :class=\"{ active: running }\" @click=\"toggle\">\n        {{ running ? '停止动画' : '开始动画' }}\n      \u003C\u002Fbutton>\n      \u003Cbutton class=\"btn ghost\" @click=\"reset\">复位\u003C\u002Fbutton>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { nextTick, onUnmounted, ref } from 'vue'\n\nconst running = ref(false)\nconst x = ref(0)\nconst transformMs = ref(0)\nconst layoutMs = ref(0)\nconst leftBall = ref(null)\n\nlet rafId = 0\nlet start = 0\nlet tSamples = []\nlet lSamples = []\n\nfunction tick(now) {\n  if (!start) start = now\n  const phase = (now - start) \u002F 420\n  const value = Math.sin(phase) * 100 + 100\n\n  const t0 = performance.now()\n  x.value = value\n  const tCost = performance.now() - t0\n  tSamples.push(tCost)\n  if (tSamples.length > 20) tSamples.shift()\n  transformMs.value = Math.round((tSamples.reduce((a, b) => a + b, 0) \u002F tSamples.length) * 100) \u002F 100\n\n  nextTick(() => {\n    const l0 = performance.now()\n    const el = leftBall.value\n    if (el) {\n      void el.offsetLeft\n      void el.offsetWidth\n      void el.getBoundingClientRect()\n    }\n    const lCost = performance.now() - l0\n    lSamples.push(lCost)\n    if (lSamples.length > 20) lSamples.shift()\n    layoutMs.value = Math.round((lSamples.reduce((a, b) => a + b, 0) \u002F lSamples.length) * 10) \u002F 10\n\n    if (running.value) {\n      rafId = requestAnimationFrame(tick)\n    }\n  })\n}\n\nfunction resetAnim() {\n  start = 0\n  x.value = 0\n  tSamples = []\n  lSamples = []\n  transformMs.value = 0\n  layoutMs.value = 0\n}\n\nfunction toggle() {\n  running.value = !running.value\n  if (running.value) {\n    resetAnim()\n    rafId = requestAnimationFrame(tick)\n  } else {\n    cancelAnimationFrame(rafId)\n  }\n}\n\nfunction reset() {\n  running.value = false\n  cancelAnimationFrame(rafId)\n  resetAnim()\n}\n\nonUnmounted(() => cancelAnimationFrame(rafId))\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n.wrap {\n  padding: 20px;\n  font-family: system-ui, sans-serif;\n}\n\n.tip {\n  margin: 0 0 14px;\n  color: #64748b;\n  font-size: 0.85rem;\n  line-height: 1.55;\n}\n\n.tip code {\n  font-size: 0.8em;\n  padding: 1px 4px;\n  border-radius: 4px;\n  background: #e2e8f0;\n}\n\n.compare {\n  display: grid;\n  gap: 12px;\n}\n\n@media (min-width: 520px) {\n  .compare {\n    grid-template-columns: 1fr 1fr;\n  }\n}\n\n.col {\n  padding: 10px;\n  border-radius: 10px;\n  background: #f8fafc;\n  border: 1px solid #e2e8f0;\n}\n\n.col-head {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  margin-bottom: 8px;\n  gap: 6px;\n}\n\n.tag {\n  font-size: 11px;\n  font-weight: 600;\n  padding: 2px 8px;\n  border-radius: 999px;\n}\n\n.tag.good {\n  background: #dcfce7;\n  color: #166534;\n}\n\n.tag.warn {\n  background: #fef3c7;\n  color: #92400e;\n}\n\n.metric {\n  font-size: 11px;\n  font-weight: 600;\n  font-variant-numeric: tabular-nums;\n  color: #166534;\n}\n\n.metric.warn-m {\n  color: #b45309;\n}\n\n.track {\n  position: relative;\n  height: 44px;\n  border-radius: 8px;\n  background: #e2e8f0;\n  overflow: hidden;\n}\n\n.ball {\n  position: absolute;\n  top: 10px;\n  left: 0;\n  width: 24px;\n  height: 24px;\n  border-radius: 50%;\n  background: #6366f1;\n  will-change: transform, left;\n}\n\n.note {\n  margin: 8px 0 0;\n  font-size: 11px;\n  color: #94a3b8;\n}\n\n.actions {\n  display: flex;\n  gap: 8px;\n  margin-top: 14px;\n}\n\n.btn {\n  padding: 8px 16px;\n  border: none;\n  border-radius: 8px;\n  background: #6366f1;\n  color: white;\n  font-weight: 600;\n  cursor: pointer;\n  font-size: 13px;\n}\n\n.btn.active {\n  background: #4f46e5;\n}\n\n.btn.ghost {\n  background: #e2e8f0;\n  color: #334155;\n}\n\u003C\u002Fstyle>\n","vue",[83,371,372,384,402,418,433,448,458,465,481,497,514,536,557,567,583,611,620,641,651,656,671,686,706,727,736,751,783,792,812,821,830,835,851,884,890,899,927,936,946,956,961,974,989,994,1013,1032,1050,1068,1087,1092,1106,1118,1131,1143,1148,1166,1186,1212,1243,1248,1266,1277,1299,1311,1336,1404,1409,1423,1440,1452,1461,1470,1478,1491,1497,1518,1529,1550,1610,1615,1623,1637,1642,1648,1654,1659,1670,1680,1689,1699,1709,1718,1728,1733,1738,1748,1762,1769,1777,1789,1800,1809,1815,1820,1825,1835,1845,1853,1861,1866,1871,1887,1896,1901,1914,1922,1940,1958,1963,1968,1976,1996,2009,2025,2038,2043,2048,2058,2073,2092,2107,2120,2125,2130,2138,2151,2166,2171,2176,2196,2204,2224,2229,2234,2239,2247,2261,2274,2286,2306,2311,2316,2324,2336,2349,2362,2377,2391,2396,2401,2409,2423,2436,2455,2469,2474,2479,2487,2499,2511,2516,2521,2529,2541,2553,2558,2563,2571,2584,2595,2608,2619,2624,2629,2637,2649,2654,2659,2667,2680,2695,2708,2719,2732,2737,2742,2750,2762,2776,2788,2803,2816,2831,2843,2856,2861,2866,2874,2891,2904,2916,2921,2926,2934,2945,2958,2973,2978,2983,2991,3009,3021,3034,3045,3057,3068,3081,3095,3100,3105,3113,3125,3130,3135,3143,3154,3166,3171],{"__ignoreMap":110},[114,373,374,377,381],{"class":116,"line":117},[114,375,376],{"class":131},"\u003C",[114,378,380],{"class":379},"s9eBZ","template",[114,382,383],{"class":131},">\n",[114,385,386,389,392,395,397,400],{"class":116,"line":149},[114,387,388],{"class":131},"  \u003C",[114,390,391],{"class":379},"div",[114,393,394],{"class":135}," class",[114,396,155],{"class":131},[114,398,399],{"class":142},"\"wrap\"",[114,401,383],{"class":131},[114,403,404,407,409,411,413,416],{"class":116,"line":161},[114,405,406],{"class":131},"    \u003C",[114,408,15],{"class":379},[114,410,394],{"class":135},[114,412,155],{"class":131},[114,414,415],{"class":142},"\"tip\"",[114,417,383],{"class":131},[114,419,420,423,425,428,430],{"class":116,"line":168},[114,421,422],{"class":131},"      点「开始动画」：两球同步往复移动。左边 \u003C",[114,424,83],{"class":379},[114,426,427],{"class":131},">transform: translateX\u003C\u002F",[114,429,83],{"class":379},[114,431,432],{"class":131},">，右边\n",[114,434,435,438,440,443,445],{"class":116,"line":180},[114,436,437],{"class":131},"      \u003C",[114,439,83],{"class":379},[114,441,442],{"class":131},">left\u003C\u002F",[114,444,83],{"class":379},[114,446,447],{"class":131},">。画面应一致；右轨每帧触发布局，主线程更忙时可能更卡。\n",[114,449,451,454,456],{"class":116,"line":450},6,[114,452,453],{"class":131},"    \u003C\u002F",[114,455,15],{"class":379},[114,457,383],{"class":131},[114,459,461],{"class":116,"line":460},7,[114,462,464],{"emptyLinePlaceholder":463},true,"\n",[114,466,468,470,472,474,476,479],{"class":116,"line":467},8,[114,469,406],{"class":131},[114,471,391],{"class":379},[114,473,394],{"class":135},[114,475,155],{"class":131},[114,477,478],{"class":142},"\"compare\"",[114,480,383],{"class":131},[114,482,484,486,488,490,492,495],{"class":116,"line":483},9,[114,485,437],{"class":131},[114,487,391],{"class":379},[114,489,394],{"class":135},[114,491,155],{"class":131},[114,493,494],{"class":142},"\"col\"",[114,496,383],{"class":131},[114,498,500,503,505,507,509,512],{"class":116,"line":499},10,[114,501,502],{"class":131},"        \u003C",[114,504,391],{"class":379},[114,506,394],{"class":135},[114,508,155],{"class":131},[114,510,511],{"class":142},"\"col-head\"",[114,513,383],{"class":131},[114,515,517,520,522,524,526,529,532,534],{"class":116,"line":516},11,[114,518,519],{"class":131},"          \u003C",[114,521,114],{"class":379},[114,523,394],{"class":135},[114,525,155],{"class":131},[114,527,528],{"class":142},"\"tag good\"",[114,530,531],{"class":131},">transform\u003C\u002F",[114,533,114],{"class":379},[114,535,383],{"class":131},[114,537,539,541,543,545,547,550,553,555],{"class":116,"line":538},12,[114,540,519],{"class":131},[114,542,114],{"class":379},[114,544,394],{"class":135},[114,546,155],{"class":131},[114,548,549],{"class":142},"\"metric\"",[114,551,552],{"class":131},">本帧 ≈ {{ transformMs }}ms\u003C\u002F",[114,554,114],{"class":379},[114,556,383],{"class":131},[114,558,560,563,565],{"class":116,"line":559},13,[114,561,562],{"class":131},"        \u003C\u002F",[114,564,391],{"class":379},[114,566,383],{"class":131},[114,568,570,572,574,576,578,581],{"class":116,"line":569},14,[114,571,502],{"class":131},[114,573,391],{"class":379},[114,575,394],{"class":135},[114,577,155],{"class":131},[114,579,580],{"class":142},"\"track\"",[114,582,383],{"class":131},[114,584,586,588,590,592,594,597,600,602,605,609],{"class":116,"line":585},15,[114,587,519],{"class":131},[114,589,391],{"class":379},[114,591,394],{"class":135},[114,593,155],{"class":131},[114,595,596],{"class":142},"\"ball\"",[114,598,599],{"class":135}," :style",[114,601,155],{"class":131},[114,603,604],{"class":142},"\"{ transform: `translateX(${x}px)` }\"",[114,606,608],{"class":607},"s7hpK"," \u002F",[114,610,383],{"class":131},[114,612,614,616,618],{"class":116,"line":613},16,[114,615,562],{"class":131},[114,617,391],{"class":379},[114,619,383],{"class":131},[114,621,623,625,627,629,631,634,637,639],{"class":116,"line":622},17,[114,624,502],{"class":131},[114,626,15],{"class":379},[114,628,394],{"class":135},[114,630,155],{"class":131},[114,632,633],{"class":142},"\"note\"",[114,635,636],{"class":131},">合成阶段位移，不改布局树\u003C\u002F",[114,638,15],{"class":379},[114,640,383],{"class":131},[114,642,644,647,649],{"class":116,"line":643},18,[114,645,646],{"class":131},"      \u003C\u002F",[114,648,391],{"class":379},[114,650,383],{"class":131},[114,652,654],{"class":116,"line":653},19,[114,655,464],{"emptyLinePlaceholder":463},[114,657,659,661,663,665,667,669],{"class":116,"line":658},20,[114,660,437],{"class":131},[114,662,391],{"class":379},[114,664,394],{"class":135},[114,666,155],{"class":131},[114,668,494],{"class":142},[114,670,383],{"class":131},[114,672,674,676,678,680,682,684],{"class":116,"line":673},21,[114,675,502],{"class":131},[114,677,391],{"class":379},[114,679,394],{"class":135},[114,681,155],{"class":131},[114,683,511],{"class":142},[114,685,383],{"class":131},[114,687,689,691,693,695,697,700,702,704],{"class":116,"line":688},22,[114,690,519],{"class":131},[114,692,114],{"class":379},[114,694,394],{"class":135},[114,696,155],{"class":131},[114,698,699],{"class":142},"\"tag warn\"",[114,701,442],{"class":131},[114,703,114],{"class":379},[114,705,383],{"class":131},[114,707,709,711,713,715,717,720,723,725],{"class":116,"line":708},23,[114,710,519],{"class":131},[114,712,114],{"class":379},[114,714,394],{"class":135},[114,716,155],{"class":131},[114,718,719],{"class":142},"\"metric warn-m\"",[114,721,722],{"class":131},">{{ layoutMs }}ms\u003C\u002F",[114,724,114],{"class":379},[114,726,383],{"class":131},[114,728,730,732,734],{"class":116,"line":729},24,[114,731,562],{"class":131},[114,733,391],{"class":379},[114,735,383],{"class":131},[114,737,739,741,743,745,747,749],{"class":116,"line":738},25,[114,740,502],{"class":131},[114,742,391],{"class":379},[114,744,394],{"class":135},[114,746,155],{"class":131},[114,748,580],{"class":142},[114,750,383],{"class":131},[114,752,754,756,758,761,763,766,768,770,772,774,776,779,781],{"class":116,"line":753},26,[114,755,519],{"class":131},[114,757,391],{"class":379},[114,759,760],{"class":135}," ref",[114,762,155],{"class":131},[114,764,765],{"class":142},"\"leftBall\"",[114,767,394],{"class":135},[114,769,155],{"class":131},[114,771,596],{"class":142},[114,773,599],{"class":135},[114,775,155],{"class":131},[114,777,778],{"class":142},"\"{ left: `${x}px` }\"",[114,780,608],{"class":607},[114,782,383],{"class":131},[114,784,786,788,790],{"class":116,"line":785},27,[114,787,562],{"class":131},[114,789,391],{"class":379},[114,791,383],{"class":131},[114,793,795,797,799,801,803,805,808,810],{"class":116,"line":794},28,[114,796,502],{"class":131},[114,798,15],{"class":379},[114,800,394],{"class":135},[114,802,155],{"class":131},[114,804,633],{"class":142},[114,806,807],{"class":131},">每帧改几何位置 → Layout\u003C\u002F",[114,809,15],{"class":379},[114,811,383],{"class":131},[114,813,815,817,819],{"class":116,"line":814},29,[114,816,646],{"class":131},[114,818,391],{"class":379},[114,820,383],{"class":131},[114,822,824,826,828],{"class":116,"line":823},30,[114,825,453],{"class":131},[114,827,391],{"class":379},[114,829,383],{"class":131},[114,831,833],{"class":116,"line":832},31,[114,834,464],{"emptyLinePlaceholder":463},[114,836,838,840,842,844,846,849],{"class":116,"line":837},32,[114,839,406],{"class":131},[114,841,391],{"class":379},[114,843,394],{"class":135},[114,845,155],{"class":131},[114,847,848],{"class":142},"\"actions\"",[114,850,383],{"class":131},[114,852,854,856,859,861,863,866,869,871,874,877,879,882],{"class":116,"line":853},33,[114,855,437],{"class":131},[114,857,858],{"class":379},"button",[114,860,394],{"class":135},[114,862,155],{"class":131},[114,864,865],{"class":142},"\"btn\"",[114,867,868],{"class":135}," :class",[114,870,155],{"class":131},[114,872,873],{"class":142},"\"{ active: running }\"",[114,875,876],{"class":135}," @click",[114,878,155],{"class":131},[114,880,881],{"class":142},"\"toggle\"",[114,883,383],{"class":131},[114,885,887],{"class":116,"line":886},34,[114,888,889],{"class":131},"        {{ running ? '停止动画' : '开始动画' }}\n",[114,891,893,895,897],{"class":116,"line":892},35,[114,894,646],{"class":131},[114,896,858],{"class":379},[114,898,383],{"class":131},[114,900,902,904,906,908,910,913,915,917,920,923,925],{"class":116,"line":901},36,[114,903,437],{"class":131},[114,905,858],{"class":379},[114,907,394],{"class":135},[114,909,155],{"class":131},[114,911,912],{"class":142},"\"btn ghost\"",[114,914,876],{"class":135},[114,916,155],{"class":131},[114,918,919],{"class":142},"\"reset\"",[114,921,922],{"class":131},">复位\u003C\u002F",[114,924,858],{"class":379},[114,926,383],{"class":131},[114,928,930,932,934],{"class":116,"line":929},37,[114,931,453],{"class":131},[114,933,391],{"class":379},[114,935,383],{"class":131},[114,937,939,942,944],{"class":116,"line":938},38,[114,940,941],{"class":131},"  \u003C\u002F",[114,943,391],{"class":379},[114,945,383],{"class":131},[114,947,949,952,954],{"class":116,"line":948},39,[114,950,951],{"class":131},"\u003C\u002F",[114,953,380],{"class":379},[114,955,383],{"class":131},[114,957,959],{"class":116,"line":958},40,[114,960,464],{"emptyLinePlaceholder":463},[114,962,964,966,969,972],{"class":116,"line":963},41,[114,965,376],{"class":131},[114,967,968],{"class":379},"script",[114,970,971],{"class":135}," setup",[114,973,383],{"class":131},[114,975,977,980,983,986],{"class":116,"line":976},42,[114,978,979],{"class":120},"import",[114,981,982],{"class":131}," { nextTick, onUnmounted, ref } ",[114,984,985],{"class":120},"from",[114,987,988],{"class":142}," 'vue'\n",[114,990,992],{"class":116,"line":991},43,[114,993,464],{"emptyLinePlaceholder":463},[114,995,997,999,1002,1004,1006,1008,1011],{"class":116,"line":996},44,[114,998,121],{"class":120},[114,1000,1001],{"class":124}," running",[114,1003,128],{"class":120},[114,1005,760],{"class":135},[114,1007,139],{"class":131},[114,1009,1010],{"class":124},"false",[114,1012,146],{"class":131},[114,1014,1016,1018,1021,1023,1025,1027,1030],{"class":116,"line":1015},45,[114,1017,121],{"class":120},[114,1019,1020],{"class":124}," x",[114,1022,128],{"class":120},[114,1024,760],{"class":135},[114,1026,139],{"class":131},[114,1028,1029],{"class":124},"0",[114,1031,146],{"class":131},[114,1033,1035,1037,1040,1042,1044,1046,1048],{"class":116,"line":1034},46,[114,1036,121],{"class":120},[114,1038,1039],{"class":124}," transformMs",[114,1041,128],{"class":120},[114,1043,760],{"class":135},[114,1045,139],{"class":131},[114,1047,1029],{"class":124},[114,1049,146],{"class":131},[114,1051,1053,1055,1058,1060,1062,1064,1066],{"class":116,"line":1052},47,[114,1054,121],{"class":120},[114,1056,1057],{"class":124}," layoutMs",[114,1059,128],{"class":120},[114,1061,760],{"class":135},[114,1063,139],{"class":131},[114,1065,1029],{"class":124},[114,1067,146],{"class":131},[114,1069,1071,1073,1076,1078,1080,1082,1085],{"class":116,"line":1070},48,[114,1072,121],{"class":120},[114,1074,1075],{"class":124}," leftBall",[114,1077,128],{"class":120},[114,1079,760],{"class":135},[114,1081,139],{"class":131},[114,1083,1084],{"class":124},"null",[114,1086,146],{"class":131},[114,1088,1090],{"class":116,"line":1089},49,[114,1091,464],{"emptyLinePlaceholder":463},[114,1093,1095,1098,1101,1103],{"class":116,"line":1094},50,[114,1096,1097],{"class":120},"let",[114,1099,1100],{"class":131}," rafId ",[114,1102,155],{"class":120},[114,1104,1105],{"class":124}," 0\n",[114,1107,1109,1111,1114,1116],{"class":116,"line":1108},51,[114,1110,1097],{"class":120},[114,1112,1113],{"class":131}," start ",[114,1115,155],{"class":120},[114,1117,1105],{"class":124},[114,1119,1121,1123,1126,1128],{"class":116,"line":1120},52,[114,1122,1097],{"class":120},[114,1124,1125],{"class":131}," tSamples ",[114,1127,155],{"class":120},[114,1129,1130],{"class":131}," []\n",[114,1132,1134,1136,1139,1141],{"class":116,"line":1133},53,[114,1135,1097],{"class":120},[114,1137,1138],{"class":131}," lSamples ",[114,1140,155],{"class":120},[114,1142,1130],{"class":131},[114,1144,1146],{"class":116,"line":1145},54,[114,1147,464],{"emptyLinePlaceholder":463},[114,1149,1151,1154,1157,1159,1163],{"class":116,"line":1150},55,[114,1152,1153],{"class":120},"function",[114,1155,1156],{"class":135}," tick",[114,1158,139],{"class":131},[114,1160,1162],{"class":1161},"s4XuR","now",[114,1164,1165],{"class":131},") {\n",[114,1167,1169,1172,1175,1178,1181,1183],{"class":116,"line":1168},56,[114,1170,1171],{"class":120},"  if",[114,1173,1174],{"class":131}," (",[114,1176,1177],{"class":120},"!",[114,1179,1180],{"class":131},"start) start ",[114,1182,155],{"class":120},[114,1184,1185],{"class":131}," now\n",[114,1187,1189,1192,1195,1197,1200,1203,1206,1209],{"class":116,"line":1188},57,[114,1190,1191],{"class":120},"  const",[114,1193,1194],{"class":124}," phase",[114,1196,128],{"class":120},[114,1198,1199],{"class":131}," (now ",[114,1201,1202],{"class":120},"-",[114,1204,1205],{"class":131}," start) ",[114,1207,1208],{"class":120},"\u002F",[114,1210,1211],{"class":124}," 420\n",[114,1213,1215,1217,1220,1222,1225,1228,1231,1234,1237,1240],{"class":116,"line":1214},58,[114,1216,1191],{"class":120},[114,1218,1219],{"class":124}," value",[114,1221,128],{"class":120},[114,1223,1224],{"class":131}," Math.",[114,1226,1227],{"class":135},"sin",[114,1229,1230],{"class":131},"(phase) ",[114,1232,1233],{"class":120},"*",[114,1235,1236],{"class":124}," 100",[114,1238,1239],{"class":120}," +",[114,1241,1242],{"class":124}," 100\n",[114,1244,1246],{"class":116,"line":1245},59,[114,1247,464],{"emptyLinePlaceholder":463},[114,1249,1251,1253,1256,1258,1261,1263],{"class":116,"line":1250},60,[114,1252,1191],{"class":120},[114,1254,1255],{"class":124}," t0",[114,1257,128],{"class":120},[114,1259,1260],{"class":131}," performance.",[114,1262,1162],{"class":135},[114,1264,1265],{"class":131},"()\n",[114,1267,1269,1272,1274],{"class":116,"line":1268},61,[114,1270,1271],{"class":131},"  x.value ",[114,1273,155],{"class":120},[114,1275,1276],{"class":131}," value\n",[114,1278,1280,1282,1285,1287,1289,1291,1294,1296],{"class":116,"line":1279},62,[114,1281,1191],{"class":120},[114,1283,1284],{"class":124}," tCost",[114,1286,128],{"class":120},[114,1288,1260],{"class":131},[114,1290,1162],{"class":135},[114,1292,1293],{"class":131},"() ",[114,1295,1202],{"class":120},[114,1297,1298],{"class":131}," t0\n",[114,1300,1302,1305,1308],{"class":116,"line":1301},63,[114,1303,1304],{"class":131},"  tSamples.",[114,1306,1307],{"class":135},"push",[114,1309,1310],{"class":131},"(tCost)\n",[114,1312,1314,1316,1319,1322,1325,1328,1331,1334],{"class":116,"line":1313},64,[114,1315,1171],{"class":120},[114,1317,1318],{"class":131}," (tSamples.",[114,1320,1321],{"class":124},"length",[114,1323,1324],{"class":120}," >",[114,1326,1327],{"class":124}," 20",[114,1329,1330],{"class":131},") tSamples.",[114,1332,1333],{"class":135},"shift",[114,1335,1265],{"class":131},[114,1337,1339,1342,1344,1346,1349,1352,1355,1358,1360,1363,1366,1369,1372,1375,1378,1381,1383,1385,1387,1390,1392,1394,1396,1398,1400,1402],{"class":116,"line":1338},65,[114,1340,1341],{"class":131},"  transformMs.value ",[114,1343,155],{"class":120},[114,1345,1224],{"class":131},[114,1347,1348],{"class":135},"round",[114,1350,1351],{"class":131},"((tSamples.",[114,1353,1354],{"class":135},"reduce",[114,1356,1357],{"class":131},"((",[114,1359,19],{"class":1161},[114,1361,1362],{"class":131},", ",[114,1364,1365],{"class":1161},"b",[114,1367,1368],{"class":131},") ",[114,1370,1371],{"class":120},"=>",[114,1373,1374],{"class":131}," a ",[114,1376,1377],{"class":120},"+",[114,1379,1380],{"class":131}," b, ",[114,1382,1029],{"class":124},[114,1384,1368],{"class":131},[114,1386,1208],{"class":120},[114,1388,1389],{"class":131}," tSamples.",[114,1391,1321],{"class":124},[114,1393,1368],{"class":131},[114,1395,1233],{"class":120},[114,1397,1236],{"class":124},[114,1399,1368],{"class":131},[114,1401,1208],{"class":120},[114,1403,1242],{"class":124},[114,1405,1407],{"class":116,"line":1406},66,[114,1408,464],{"emptyLinePlaceholder":463},[114,1410,1412,1415,1418,1420],{"class":116,"line":1411},67,[114,1413,1414],{"class":135},"  nextTick",[114,1416,1417],{"class":131},"(() ",[114,1419,1371],{"class":120},[114,1421,1422],{"class":131}," {\n",[114,1424,1426,1429,1432,1434,1436,1438],{"class":116,"line":1425},68,[114,1427,1428],{"class":120},"    const",[114,1430,1431],{"class":124}," l0",[114,1433,128],{"class":120},[114,1435,1260],{"class":131},[114,1437,1162],{"class":135},[114,1439,1265],{"class":131},[114,1441,1443,1445,1447,1449],{"class":116,"line":1442},69,[114,1444,1428],{"class":120},[114,1446,125],{"class":124},[114,1448,128],{"class":120},[114,1450,1451],{"class":131}," leftBall.value\n",[114,1453,1455,1458],{"class":116,"line":1454},70,[114,1456,1457],{"class":120},"    if",[114,1459,1460],{"class":131}," (el) {\n",[114,1462,1464,1467],{"class":116,"line":1463},71,[114,1465,1466],{"class":120},"      void",[114,1468,1469],{"class":131}," el.offsetLeft\n",[114,1471,1473,1475],{"class":116,"line":1472},72,[114,1474,1466],{"class":120},[114,1476,1477],{"class":131}," el.offsetWidth\n",[114,1479,1481,1483,1486,1489],{"class":116,"line":1480},73,[114,1482,1466],{"class":120},[114,1484,1485],{"class":131}," el.",[114,1487,1488],{"class":135},"getBoundingClientRect",[114,1490,1265],{"class":131},[114,1492,1494],{"class":116,"line":1493},74,[114,1495,1496],{"class":131},"    }\n",[114,1498,1500,1502,1505,1507,1509,1511,1513,1515],{"class":116,"line":1499},75,[114,1501,1428],{"class":120},[114,1503,1504],{"class":124}," lCost",[114,1506,128],{"class":120},[114,1508,1260],{"class":131},[114,1510,1162],{"class":135},[114,1512,1293],{"class":131},[114,1514,1202],{"class":120},[114,1516,1517],{"class":131}," l0\n",[114,1519,1521,1524,1526],{"class":116,"line":1520},76,[114,1522,1523],{"class":131},"    lSamples.",[114,1525,1307],{"class":135},[114,1527,1528],{"class":131},"(lCost)\n",[114,1530,1532,1534,1537,1539,1541,1543,1546,1548],{"class":116,"line":1531},77,[114,1533,1457],{"class":120},[114,1535,1536],{"class":131}," (lSamples.",[114,1538,1321],{"class":124},[114,1540,1324],{"class":120},[114,1542,1327],{"class":124},[114,1544,1545],{"class":131},") lSamples.",[114,1547,1333],{"class":135},[114,1549,1265],{"class":131},[114,1551,1553,1556,1558,1560,1562,1565,1567,1569,1571,1573,1575,1577,1579,1581,1583,1585,1587,1589,1591,1594,1596,1598,1600,1603,1605,1607],{"class":116,"line":1552},78,[114,1554,1555],{"class":131},"    layoutMs.value ",[114,1557,155],{"class":120},[114,1559,1224],{"class":131},[114,1561,1348],{"class":135},[114,1563,1564],{"class":131},"((lSamples.",[114,1566,1354],{"class":135},[114,1568,1357],{"class":131},[114,1570,19],{"class":1161},[114,1572,1362],{"class":131},[114,1574,1365],{"class":1161},[114,1576,1368],{"class":131},[114,1578,1371],{"class":120},[114,1580,1374],{"class":131},[114,1582,1377],{"class":120},[114,1584,1380],{"class":131},[114,1586,1029],{"class":124},[114,1588,1368],{"class":131},[114,1590,1208],{"class":120},[114,1592,1593],{"class":131}," lSamples.",[114,1595,1321],{"class":124},[114,1597,1368],{"class":131},[114,1599,1233],{"class":120},[114,1601,1602],{"class":124}," 10",[114,1604,1368],{"class":131},[114,1606,1208],{"class":120},[114,1608,1609],{"class":124}," 10\n",[114,1611,1613],{"class":116,"line":1612},79,[114,1614,464],{"emptyLinePlaceholder":463},[114,1616,1618,1620],{"class":116,"line":1617},80,[114,1619,1457],{"class":120},[114,1621,1622],{"class":131}," (running.value) {\n",[114,1624,1626,1629,1631,1634],{"class":116,"line":1625},81,[114,1627,1628],{"class":131},"      rafId ",[114,1630,155],{"class":120},[114,1632,1633],{"class":135}," requestAnimationFrame",[114,1635,1636],{"class":131},"(tick)\n",[114,1638,1640],{"class":116,"line":1639},82,[114,1641,1496],{"class":131},[114,1643,1645],{"class":116,"line":1644},83,[114,1646,1647],{"class":131},"  })\n",[114,1649,1651],{"class":116,"line":1650},84,[114,1652,1653],{"class":131},"}\n",[114,1655,1657],{"class":116,"line":1656},85,[114,1658,464],{"emptyLinePlaceholder":463},[114,1660,1662,1664,1667],{"class":116,"line":1661},86,[114,1663,1153],{"class":120},[114,1665,1666],{"class":135}," resetAnim",[114,1668,1669],{"class":131},"() {\n",[114,1671,1673,1676,1678],{"class":116,"line":1672},87,[114,1674,1675],{"class":131},"  start ",[114,1677,155],{"class":120},[114,1679,1105],{"class":124},[114,1681,1683,1685,1687],{"class":116,"line":1682},88,[114,1684,1271],{"class":131},[114,1686,155],{"class":120},[114,1688,1105],{"class":124},[114,1690,1692,1695,1697],{"class":116,"line":1691},89,[114,1693,1694],{"class":131},"  tSamples ",[114,1696,155],{"class":120},[114,1698,1130],{"class":131},[114,1700,1702,1705,1707],{"class":116,"line":1701},90,[114,1703,1704],{"class":131},"  lSamples ",[114,1706,155],{"class":120},[114,1708,1130],{"class":131},[114,1710,1712,1714,1716],{"class":116,"line":1711},91,[114,1713,1341],{"class":131},[114,1715,155],{"class":120},[114,1717,1105],{"class":124},[114,1719,1721,1724,1726],{"class":116,"line":1720},92,[114,1722,1723],{"class":131},"  layoutMs.value ",[114,1725,155],{"class":120},[114,1727,1105],{"class":124},[114,1729,1731],{"class":116,"line":1730},93,[114,1732,1653],{"class":131},[114,1734,1736],{"class":116,"line":1735},94,[114,1737,464],{"emptyLinePlaceholder":463},[114,1739,1741,1743,1746],{"class":116,"line":1740},95,[114,1742,1153],{"class":120},[114,1744,1745],{"class":135}," toggle",[114,1747,1669],{"class":131},[114,1749,1751,1754,1756,1759],{"class":116,"line":1750},96,[114,1752,1753],{"class":131},"  running.value ",[114,1755,155],{"class":120},[114,1757,1758],{"class":120}," !",[114,1760,1761],{"class":131},"running.value\n",[114,1763,1765,1767],{"class":116,"line":1764},97,[114,1766,1171],{"class":120},[114,1768,1622],{"class":131},[114,1770,1772,1775],{"class":116,"line":1771},98,[114,1773,1774],{"class":135},"    resetAnim",[114,1776,1265],{"class":131},[114,1778,1780,1783,1785,1787],{"class":116,"line":1779},99,[114,1781,1782],{"class":131},"    rafId ",[114,1784,155],{"class":120},[114,1786,1633],{"class":135},[114,1788,1636],{"class":131},[114,1790,1792,1795,1798],{"class":116,"line":1791},100,[114,1793,1794],{"class":131},"  } ",[114,1796,1797],{"class":120},"else",[114,1799,1422],{"class":131},[114,1801,1803,1806],{"class":116,"line":1802},101,[114,1804,1805],{"class":135},"    cancelAnimationFrame",[114,1807,1808],{"class":131},"(rafId)\n",[114,1810,1812],{"class":116,"line":1811},102,[114,1813,1814],{"class":131},"  }\n",[114,1816,1818],{"class":116,"line":1817},103,[114,1819,1653],{"class":131},[114,1821,1823],{"class":116,"line":1822},104,[114,1824,464],{"emptyLinePlaceholder":463},[114,1826,1828,1830,1833],{"class":116,"line":1827},105,[114,1829,1153],{"class":120},[114,1831,1832],{"class":135}," reset",[114,1834,1669],{"class":131},[114,1836,1838,1840,1842],{"class":116,"line":1837},106,[114,1839,1753],{"class":131},[114,1841,155],{"class":120},[114,1843,1844],{"class":124}," false\n",[114,1846,1848,1851],{"class":116,"line":1847},107,[114,1849,1850],{"class":135},"  cancelAnimationFrame",[114,1852,1808],{"class":131},[114,1854,1856,1859],{"class":116,"line":1855},108,[114,1857,1858],{"class":135},"  resetAnim",[114,1860,1265],{"class":131},[114,1862,1864],{"class":116,"line":1863},109,[114,1865,1653],{"class":131},[114,1867,1869],{"class":116,"line":1868},110,[114,1870,464],{"emptyLinePlaceholder":463},[114,1872,1874,1877,1879,1881,1884],{"class":116,"line":1873},111,[114,1875,1876],{"class":135},"onUnmounted",[114,1878,1417],{"class":131},[114,1880,1371],{"class":120},[114,1882,1883],{"class":135}," cancelAnimationFrame",[114,1885,1886],{"class":131},"(rafId))\n",[114,1888,1890,1892,1894],{"class":116,"line":1889},112,[114,1891,951],{"class":131},[114,1893,968],{"class":379},[114,1895,383],{"class":131},[114,1897,1899],{"class":116,"line":1898},113,[114,1900,464],{"emptyLinePlaceholder":463},[114,1902,1904,1906,1909,1912],{"class":116,"line":1903},114,[114,1905,376],{"class":131},[114,1907,1908],{"class":379},"style",[114,1910,1911],{"class":135}," scoped",[114,1913,383],{"class":131},[114,1915,1917,1920],{"class":116,"line":1916},115,[114,1918,1919],{"class":135},".wrap",[114,1921,1422],{"class":131},[114,1923,1925,1928,1931,1934,1937],{"class":116,"line":1924},116,[114,1926,1927],{"class":124},"  padding",[114,1929,1930],{"class":131},": ",[114,1932,1933],{"class":124},"20",[114,1935,1936],{"class":120},"px",[114,1938,1939],{"class":131},";\n",[114,1941,1943,1946,1948,1951,1953,1956],{"class":116,"line":1942},117,[114,1944,1945],{"class":124},"  font-family",[114,1947,1930],{"class":131},[114,1949,1950],{"class":124},"system-ui",[114,1952,1362],{"class":131},[114,1954,1955],{"class":124},"sans-serif",[114,1957,1939],{"class":131},[114,1959,1961],{"class":116,"line":1960},118,[114,1962,1653],{"class":131},[114,1964,1966],{"class":116,"line":1965},119,[114,1967,464],{"emptyLinePlaceholder":463},[114,1969,1971,1974],{"class":116,"line":1970},120,[114,1972,1973],{"class":135},".tip",[114,1975,1422],{"class":131},[114,1977,1979,1982,1984,1986,1989,1992,1994],{"class":116,"line":1978},121,[114,1980,1981],{"class":124},"  margin",[114,1983,1930],{"class":131},[114,1985,1029],{"class":124},[114,1987,1988],{"class":124}," 0",[114,1990,1991],{"class":124}," 14",[114,1993,1936],{"class":120},[114,1995,1939],{"class":131},[114,1997,1999,2002,2004,2007],{"class":116,"line":1998},122,[114,2000,2001],{"class":124},"  color",[114,2003,1930],{"class":131},[114,2005,2006],{"class":124},"#64748b",[114,2008,1939],{"class":131},[114,2010,2012,2015,2017,2020,2023],{"class":116,"line":2011},123,[114,2013,2014],{"class":124},"  font-size",[114,2016,1930],{"class":131},[114,2018,2019],{"class":124},"0.85",[114,2021,2022],{"class":120},"rem",[114,2024,1939],{"class":131},[114,2026,2028,2031,2033,2036],{"class":116,"line":2027},124,[114,2029,2030],{"class":124},"  line-height",[114,2032,1930],{"class":131},[114,2034,2035],{"class":124},"1.55",[114,2037,1939],{"class":131},[114,2039,2041],{"class":116,"line":2040},125,[114,2042,1653],{"class":131},[114,2044,2046],{"class":116,"line":2045},126,[114,2047,464],{"emptyLinePlaceholder":463},[114,2049,2051,2053,2056],{"class":116,"line":2050},127,[114,2052,1973],{"class":135},[114,2054,2055],{"class":379}," code",[114,2057,1422],{"class":131},[114,2059,2061,2063,2065,2068,2071],{"class":116,"line":2060},128,[114,2062,2014],{"class":124},[114,2064,1930],{"class":131},[114,2066,2067],{"class":124},"0.8",[114,2069,2070],{"class":120},"em",[114,2072,1939],{"class":131},[114,2074,2076,2078,2080,2083,2085,2088,2090],{"class":116,"line":2075},129,[114,2077,1927],{"class":124},[114,2079,1930],{"class":131},[114,2081,2082],{"class":124},"1",[114,2084,1936],{"class":120},[114,2086,2087],{"class":124}," 4",[114,2089,1936],{"class":120},[114,2091,1939],{"class":131},[114,2093,2095,2098,2100,2103,2105],{"class":116,"line":2094},130,[114,2096,2097],{"class":124},"  border-radius",[114,2099,1930],{"class":131},[114,2101,2102],{"class":124},"4",[114,2104,1936],{"class":120},[114,2106,1939],{"class":131},[114,2108,2110,2113,2115,2118],{"class":116,"line":2109},131,[114,2111,2112],{"class":124},"  background",[114,2114,1930],{"class":131},[114,2116,2117],{"class":124},"#e2e8f0",[114,2119,1939],{"class":131},[114,2121,2123],{"class":116,"line":2122},132,[114,2124,1653],{"class":131},[114,2126,2128],{"class":116,"line":2127},133,[114,2129,464],{"emptyLinePlaceholder":463},[114,2131,2133,2136],{"class":116,"line":2132},134,[114,2134,2135],{"class":135},".compare",[114,2137,1422],{"class":131},[114,2139,2141,2144,2146,2149],{"class":116,"line":2140},135,[114,2142,2143],{"class":124},"  display",[114,2145,1930],{"class":131},[114,2147,2148],{"class":124},"grid",[114,2150,1939],{"class":131},[114,2152,2154,2157,2159,2162,2164],{"class":116,"line":2153},136,[114,2155,2156],{"class":124},"  gap",[114,2158,1930],{"class":131},[114,2160,2161],{"class":124},"12",[114,2163,1936],{"class":120},[114,2165,1939],{"class":131},[114,2167,2169],{"class":116,"line":2168},137,[114,2170,1653],{"class":131},[114,2172,2174],{"class":116,"line":2173},138,[114,2175,464],{"emptyLinePlaceholder":463},[114,2177,2179,2182,2184,2187,2189,2192,2194],{"class":116,"line":2178},139,[114,2180,2181],{"class":120},"@media",[114,2183,1174],{"class":131},[114,2185,2186],{"class":124},"min-width",[114,2188,1930],{"class":131},[114,2190,2191],{"class":124},"520",[114,2193,1936],{"class":120},[114,2195,1165],{"class":131},[114,2197,2199,2202],{"class":116,"line":2198},140,[114,2200,2201],{"class":135},"  .compare",[114,2203,1422],{"class":131},[114,2205,2207,2210,2212,2214,2217,2220,2222],{"class":116,"line":2206},141,[114,2208,2209],{"class":124},"    grid-template-columns",[114,2211,1930],{"class":131},[114,2213,2082],{"class":124},[114,2215,2216],{"class":120},"fr",[114,2218,2219],{"class":124}," 1",[114,2221,2216],{"class":120},[114,2223,1939],{"class":131},[114,2225,2227],{"class":116,"line":2226},142,[114,2228,1814],{"class":131},[114,2230,2232],{"class":116,"line":2231},143,[114,2233,1653],{"class":131},[114,2235,2237],{"class":116,"line":2236},144,[114,2238,464],{"emptyLinePlaceholder":463},[114,2240,2242,2245],{"class":116,"line":2241},145,[114,2243,2244],{"class":135},".col",[114,2246,1422],{"class":131},[114,2248,2250,2252,2254,2257,2259],{"class":116,"line":2249},146,[114,2251,1927],{"class":124},[114,2253,1930],{"class":131},[114,2255,2256],{"class":124},"10",[114,2258,1936],{"class":120},[114,2260,1939],{"class":131},[114,2262,2264,2266,2268,2270,2272],{"class":116,"line":2263},147,[114,2265,2097],{"class":124},[114,2267,1930],{"class":131},[114,2269,2256],{"class":124},[114,2271,1936],{"class":120},[114,2273,1939],{"class":131},[114,2275,2277,2279,2281,2284],{"class":116,"line":2276},148,[114,2278,2112],{"class":124},[114,2280,1930],{"class":131},[114,2282,2283],{"class":124},"#f8fafc",[114,2285,1939],{"class":131},[114,2287,2289,2292,2294,2296,2298,2301,2304],{"class":116,"line":2288},149,[114,2290,2291],{"class":124},"  border",[114,2293,1930],{"class":131},[114,2295,2082],{"class":124},[114,2297,1936],{"class":120},[114,2299,2300],{"class":124}," solid",[114,2302,2303],{"class":124}," #e2e8f0",[114,2305,1939],{"class":131},[114,2307,2309],{"class":116,"line":2308},150,[114,2310,1653],{"class":131},[114,2312,2314],{"class":116,"line":2313},151,[114,2315,464],{"emptyLinePlaceholder":463},[114,2317,2319,2322],{"class":116,"line":2318},152,[114,2320,2321],{"class":135},".col-head",[114,2323,1422],{"class":131},[114,2325,2327,2329,2331,2334],{"class":116,"line":2326},153,[114,2328,2143],{"class":124},[114,2330,1930],{"class":131},[114,2332,2333],{"class":124},"flex",[114,2335,1939],{"class":131},[114,2337,2339,2342,2344,2347],{"class":116,"line":2338},154,[114,2340,2341],{"class":124},"  align-items",[114,2343,1930],{"class":131},[114,2345,2346],{"class":124},"center",[114,2348,1939],{"class":131},[114,2350,2352,2355,2357,2360],{"class":116,"line":2351},155,[114,2353,2354],{"class":124},"  justify-content",[114,2356,1930],{"class":131},[114,2358,2359],{"class":124},"space-between",[114,2361,1939],{"class":131},[114,2363,2365,2368,2370,2373,2375],{"class":116,"line":2364},156,[114,2366,2367],{"class":124},"  margin-bottom",[114,2369,1930],{"class":131},[114,2371,2372],{"class":124},"8",[114,2374,1936],{"class":120},[114,2376,1939],{"class":131},[114,2378,2380,2382,2384,2387,2389],{"class":116,"line":2379},157,[114,2381,2156],{"class":124},[114,2383,1930],{"class":131},[114,2385,2386],{"class":124},"6",[114,2388,1936],{"class":120},[114,2390,1939],{"class":131},[114,2392,2394],{"class":116,"line":2393},158,[114,2395,1653],{"class":131},[114,2397,2399],{"class":116,"line":2398},159,[114,2400,464],{"emptyLinePlaceholder":463},[114,2402,2404,2407],{"class":116,"line":2403},160,[114,2405,2406],{"class":135},".tag",[114,2408,1422],{"class":131},[114,2410,2412,2414,2416,2419,2421],{"class":116,"line":2411},161,[114,2413,2014],{"class":124},[114,2415,1930],{"class":131},[114,2417,2418],{"class":124},"11",[114,2420,1936],{"class":120},[114,2422,1939],{"class":131},[114,2424,2426,2429,2431,2434],{"class":116,"line":2425},162,[114,2427,2428],{"class":124},"  font-weight",[114,2430,1930],{"class":131},[114,2432,2433],{"class":124},"600",[114,2435,1939],{"class":131},[114,2437,2439,2441,2443,2446,2448,2451,2453],{"class":116,"line":2438},163,[114,2440,1927],{"class":124},[114,2442,1930],{"class":131},[114,2444,2445],{"class":124},"2",[114,2447,1936],{"class":120},[114,2449,2450],{"class":124}," 8",[114,2452,1936],{"class":120},[114,2454,1939],{"class":131},[114,2456,2458,2460,2462,2465,2467],{"class":116,"line":2457},164,[114,2459,2097],{"class":124},[114,2461,1930],{"class":131},[114,2463,2464],{"class":124},"999",[114,2466,1936],{"class":120},[114,2468,1939],{"class":131},[114,2470,2472],{"class":116,"line":2471},165,[114,2473,1653],{"class":131},[114,2475,2477],{"class":116,"line":2476},166,[114,2478,464],{"emptyLinePlaceholder":463},[114,2480,2482,2485],{"class":116,"line":2481},167,[114,2483,2484],{"class":135},".tag.good",[114,2486,1422],{"class":131},[114,2488,2490,2492,2494,2497],{"class":116,"line":2489},168,[114,2491,2112],{"class":124},[114,2493,1930],{"class":131},[114,2495,2496],{"class":124},"#dcfce7",[114,2498,1939],{"class":131},[114,2500,2502,2504,2506,2509],{"class":116,"line":2501},169,[114,2503,2001],{"class":124},[114,2505,1930],{"class":131},[114,2507,2508],{"class":124},"#166534",[114,2510,1939],{"class":131},[114,2512,2514],{"class":116,"line":2513},170,[114,2515,1653],{"class":131},[114,2517,2519],{"class":116,"line":2518},171,[114,2520,464],{"emptyLinePlaceholder":463},[114,2522,2524,2527],{"class":116,"line":2523},172,[114,2525,2526],{"class":135},".tag.warn",[114,2528,1422],{"class":131},[114,2530,2532,2534,2536,2539],{"class":116,"line":2531},173,[114,2533,2112],{"class":124},[114,2535,1930],{"class":131},[114,2537,2538],{"class":124},"#fef3c7",[114,2540,1939],{"class":131},[114,2542,2544,2546,2548,2551],{"class":116,"line":2543},174,[114,2545,2001],{"class":124},[114,2547,1930],{"class":131},[114,2549,2550],{"class":124},"#92400e",[114,2552,1939],{"class":131},[114,2554,2556],{"class":116,"line":2555},175,[114,2557,1653],{"class":131},[114,2559,2561],{"class":116,"line":2560},176,[114,2562,464],{"emptyLinePlaceholder":463},[114,2564,2566,2569],{"class":116,"line":2565},177,[114,2567,2568],{"class":135},".metric",[114,2570,1422],{"class":131},[114,2572,2574,2576,2578,2580,2582],{"class":116,"line":2573},178,[114,2575,2014],{"class":124},[114,2577,1930],{"class":131},[114,2579,2418],{"class":124},[114,2581,1936],{"class":120},[114,2583,1939],{"class":131},[114,2585,2587,2589,2591,2593],{"class":116,"line":2586},179,[114,2588,2428],{"class":124},[114,2590,1930],{"class":131},[114,2592,2433],{"class":124},[114,2594,1939],{"class":131},[114,2596,2598,2601,2603,2606],{"class":116,"line":2597},180,[114,2599,2600],{"class":124},"  font-variant-numeric",[114,2602,1930],{"class":131},[114,2604,2605],{"class":124},"tabular-nums",[114,2607,1939],{"class":131},[114,2609,2611,2613,2615,2617],{"class":116,"line":2610},181,[114,2612,2001],{"class":124},[114,2614,1930],{"class":131},[114,2616,2508],{"class":124},[114,2618,1939],{"class":131},[114,2620,2622],{"class":116,"line":2621},182,[114,2623,1653],{"class":131},[114,2625,2627],{"class":116,"line":2626},183,[114,2628,464],{"emptyLinePlaceholder":463},[114,2630,2632,2635],{"class":116,"line":2631},184,[114,2633,2634],{"class":135},".metric.warn-m",[114,2636,1422],{"class":131},[114,2638,2640,2642,2644,2647],{"class":116,"line":2639},185,[114,2641,2001],{"class":124},[114,2643,1930],{"class":131},[114,2645,2646],{"class":124},"#b45309",[114,2648,1939],{"class":131},[114,2650,2652],{"class":116,"line":2651},186,[114,2653,1653],{"class":131},[114,2655,2657],{"class":116,"line":2656},187,[114,2658,464],{"emptyLinePlaceholder":463},[114,2660,2662,2665],{"class":116,"line":2661},188,[114,2663,2664],{"class":135},".track",[114,2666,1422],{"class":131},[114,2668,2670,2673,2675,2678],{"class":116,"line":2669},189,[114,2671,2672],{"class":124},"  position",[114,2674,1930],{"class":131},[114,2676,2677],{"class":124},"relative",[114,2679,1939],{"class":131},[114,2681,2683,2686,2688,2691,2693],{"class":116,"line":2682},190,[114,2684,2685],{"class":124},"  height",[114,2687,1930],{"class":131},[114,2689,2690],{"class":124},"44",[114,2692,1936],{"class":120},[114,2694,1939],{"class":131},[114,2696,2698,2700,2702,2704,2706],{"class":116,"line":2697},191,[114,2699,2097],{"class":124},[114,2701,1930],{"class":131},[114,2703,2372],{"class":124},[114,2705,1936],{"class":120},[114,2707,1939],{"class":131},[114,2709,2711,2713,2715,2717],{"class":116,"line":2710},192,[114,2712,2112],{"class":124},[114,2714,1930],{"class":131},[114,2716,2117],{"class":124},[114,2718,1939],{"class":131},[114,2720,2722,2725,2727,2730],{"class":116,"line":2721},193,[114,2723,2724],{"class":124},"  overflow",[114,2726,1930],{"class":131},[114,2728,2729],{"class":124},"hidden",[114,2731,1939],{"class":131},[114,2733,2735],{"class":116,"line":2734},194,[114,2736,1653],{"class":131},[114,2738,2740],{"class":116,"line":2739},195,[114,2741,464],{"emptyLinePlaceholder":463},[114,2743,2745,2748],{"class":116,"line":2744},196,[114,2746,2747],{"class":135},".ball",[114,2749,1422],{"class":131},[114,2751,2753,2755,2757,2760],{"class":116,"line":2752},197,[114,2754,2672],{"class":124},[114,2756,1930],{"class":131},[114,2758,2759],{"class":124},"absolute",[114,2761,1939],{"class":131},[114,2763,2765,2768,2770,2772,2774],{"class":116,"line":2764},198,[114,2766,2767],{"class":124},"  top",[114,2769,1930],{"class":131},[114,2771,2256],{"class":124},[114,2773,1936],{"class":120},[114,2775,1939],{"class":131},[114,2777,2779,2782,2784,2786],{"class":116,"line":2778},199,[114,2780,2781],{"class":124},"  left",[114,2783,1930],{"class":131},[114,2785,1029],{"class":124},[114,2787,1939],{"class":131},[114,2789,2791,2794,2796,2799,2801],{"class":116,"line":2790},200,[114,2792,2793],{"class":124},"  width",[114,2795,1930],{"class":131},[114,2797,2798],{"class":124},"24",[114,2800,1936],{"class":120},[114,2802,1939],{"class":131},[114,2804,2806,2808,2810,2812,2814],{"class":116,"line":2805},201,[114,2807,2685],{"class":124},[114,2809,1930],{"class":131},[114,2811,2798],{"class":124},[114,2813,1936],{"class":120},[114,2815,1939],{"class":131},[114,2817,2819,2821,2823,2826,2829],{"class":116,"line":2818},202,[114,2820,2097],{"class":124},[114,2822,1930],{"class":131},[114,2824,2825],{"class":124},"50",[114,2827,2828],{"class":120},"%",[114,2830,1939],{"class":131},[114,2832,2834,2836,2838,2841],{"class":116,"line":2833},203,[114,2835,2112],{"class":124},[114,2837,1930],{"class":131},[114,2839,2840],{"class":124},"#6366f1",[114,2842,1939],{"class":131},[114,2844,2846,2849,2852,2854],{"class":116,"line":2845},204,[114,2847,2848],{"class":124},"  will-change",[114,2850,2851],{"class":131},": transform, ",[114,2853,340],{"class":124},[114,2855,1939],{"class":131},[114,2857,2859],{"class":116,"line":2858},205,[114,2860,1653],{"class":131},[114,2862,2864],{"class":116,"line":2863},206,[114,2865,464],{"emptyLinePlaceholder":463},[114,2867,2869,2872],{"class":116,"line":2868},207,[114,2870,2871],{"class":135},".note",[114,2873,1422],{"class":131},[114,2875,2877,2879,2881,2883,2885,2887,2889],{"class":116,"line":2876},208,[114,2878,1981],{"class":124},[114,2880,1930],{"class":131},[114,2882,2372],{"class":124},[114,2884,1936],{"class":120},[114,2886,1988],{"class":124},[114,2888,1988],{"class":124},[114,2890,1939],{"class":131},[114,2892,2894,2896,2898,2900,2902],{"class":116,"line":2893},209,[114,2895,2014],{"class":124},[114,2897,1930],{"class":131},[114,2899,2418],{"class":124},[114,2901,1936],{"class":120},[114,2903,1939],{"class":131},[114,2905,2907,2909,2911,2914],{"class":116,"line":2906},210,[114,2908,2001],{"class":124},[114,2910,1930],{"class":131},[114,2912,2913],{"class":124},"#94a3b8",[114,2915,1939],{"class":131},[114,2917,2919],{"class":116,"line":2918},211,[114,2920,1653],{"class":131},[114,2922,2924],{"class":116,"line":2923},212,[114,2925,464],{"emptyLinePlaceholder":463},[114,2927,2929,2932],{"class":116,"line":2928},213,[114,2930,2931],{"class":135},".actions",[114,2933,1422],{"class":131},[114,2935,2937,2939,2941,2943],{"class":116,"line":2936},214,[114,2938,2143],{"class":124},[114,2940,1930],{"class":131},[114,2942,2333],{"class":124},[114,2944,1939],{"class":131},[114,2946,2948,2950,2952,2954,2956],{"class":116,"line":2947},215,[114,2949,2156],{"class":124},[114,2951,1930],{"class":131},[114,2953,2372],{"class":124},[114,2955,1936],{"class":120},[114,2957,1939],{"class":131},[114,2959,2961,2964,2966,2969,2971],{"class":116,"line":2960},216,[114,2962,2963],{"class":124},"  margin-top",[114,2965,1930],{"class":131},[114,2967,2968],{"class":124},"14",[114,2970,1936],{"class":120},[114,2972,1939],{"class":131},[114,2974,2976],{"class":116,"line":2975},217,[114,2977,1653],{"class":131},[114,2979,2981],{"class":116,"line":2980},218,[114,2982,464],{"emptyLinePlaceholder":463},[114,2984,2986,2989],{"class":116,"line":2985},219,[114,2987,2988],{"class":135},".btn",[114,2990,1422],{"class":131},[114,2992,2994,2996,2998,3000,3002,3005,3007],{"class":116,"line":2993},220,[114,2995,1927],{"class":124},[114,2997,1930],{"class":131},[114,2999,2372],{"class":124},[114,3001,1936],{"class":120},[114,3003,3004],{"class":124}," 16",[114,3006,1936],{"class":120},[114,3008,1939],{"class":131},[114,3010,3012,3014,3016,3019],{"class":116,"line":3011},221,[114,3013,2291],{"class":124},[114,3015,1930],{"class":131},[114,3017,3018],{"class":124},"none",[114,3020,1939],{"class":131},[114,3022,3024,3026,3028,3030,3032],{"class":116,"line":3023},222,[114,3025,2097],{"class":124},[114,3027,1930],{"class":131},[114,3029,2372],{"class":124},[114,3031,1936],{"class":120},[114,3033,1939],{"class":131},[114,3035,3037,3039,3041,3043],{"class":116,"line":3036},223,[114,3038,2112],{"class":124},[114,3040,1930],{"class":131},[114,3042,2840],{"class":124},[114,3044,1939],{"class":131},[114,3046,3048,3050,3052,3055],{"class":116,"line":3047},224,[114,3049,2001],{"class":124},[114,3051,1930],{"class":131},[114,3053,3054],{"class":124},"white",[114,3056,1939],{"class":131},[114,3058,3060,3062,3064,3066],{"class":116,"line":3059},225,[114,3061,2428],{"class":124},[114,3063,1930],{"class":131},[114,3065,2433],{"class":124},[114,3067,1939],{"class":131},[114,3069,3071,3074,3076,3079],{"class":116,"line":3070},226,[114,3072,3073],{"class":124},"  cursor",[114,3075,1930],{"class":131},[114,3077,3078],{"class":124},"pointer",[114,3080,1939],{"class":131},[114,3082,3084,3086,3088,3091,3093],{"class":116,"line":3083},227,[114,3085,2014],{"class":124},[114,3087,1930],{"class":131},[114,3089,3090],{"class":124},"13",[114,3092,1936],{"class":120},[114,3094,1939],{"class":131},[114,3096,3098],{"class":116,"line":3097},228,[114,3099,1653],{"class":131},[114,3101,3103],{"class":116,"line":3102},229,[114,3104,464],{"emptyLinePlaceholder":463},[114,3106,3108,3111],{"class":116,"line":3107},230,[114,3109,3110],{"class":135},".btn.active",[114,3112,1422],{"class":131},[114,3114,3116,3118,3120,3123],{"class":116,"line":3115},231,[114,3117,2112],{"class":124},[114,3119,1930],{"class":131},[114,3121,3122],{"class":124},"#4f46e5",[114,3124,1939],{"class":131},[114,3126,3128],{"class":116,"line":3127},232,[114,3129,1653],{"class":131},[114,3131,3133],{"class":116,"line":3132},233,[114,3134,464],{"emptyLinePlaceholder":463},[114,3136,3138,3141],{"class":116,"line":3137},234,[114,3139,3140],{"class":135},".btn.ghost",[114,3142,1422],{"class":131},[114,3144,3146,3148,3150,3152],{"class":116,"line":3145},235,[114,3147,2112],{"class":124},[114,3149,1930],{"class":131},[114,3151,2117],{"class":124},[114,3153,1939],{"class":131},[114,3155,3157,3159,3161,3164],{"class":116,"line":3156},236,[114,3158,2001],{"class":124},[114,3160,1930],{"class":131},[114,3162,3163],{"class":124},"#334155",[114,3165,1939],{"class":131},[114,3167,3169],{"class":116,"line":3168},237,[114,3170,1653],{"class":131},[114,3172,3174,3176,3178],{"class":116,"line":3173},238,[114,3175,951],{"class":131},[114,3177,1908],{"class":379},[114,3179,383],{"class":131},[38,3181,3182],{"id":3182},"小结",[312,3184,3185,3194,3199],{},[315,3186,3187,3189,3190,3193],{},[25,3188,260],{}," = 重算布局树，代价高；浏览器会批量，但 ",[25,3191,3192],{},"读布局属性会强制同步","。",[315,3195,3196,3198],{},[25,3197,216],{}," = 重算绘制及之后；reflow 必然连带 repaint。",[315,3200,3201,3203,3204,3206,3207,337,3209,3193],{},[25,3202,296],{}," 主要在合成阶段完成，适合做位移、旋转、缩放动画；位移动画优先 ",[83,3205,296],{},"，慎用 ",[83,3208,336],{},[83,3210,340],{},[1908,3212,3213],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .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);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .s7hpK, html code.shiki .s7hpK{--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":110,"searchDepth":161,"depth":161,"links":3215},[3216,3220,3221,3222],{"id":40,"depth":149,"text":41,"children":3217},[3218,3219],{"id":57,"depth":161,"text":57},{"id":73,"depth":161,"text":74},{"id":190,"depth":149,"text":191},{"id":290,"depth":149,"text":291},{"id":3182,"depth":149,"text":3182},"frontend","2026-06-26","回流与重绘分别对应布局树与绘制指令的重新计算，以及为何 transform 动画大多不拖累渲染主线程。",false,"md",{},"\u002Fblog\u002Freflow-repaint-transform",{"title":6,"description":3225},"blog\u002Freflow-repaint-transform",[3233,3234,3235],"browser","rendering","performance","9qGt4nC5IYw-1z--ZpE9qqITwfWBE18SKN7ZWLP5isc",{"id":3238,"title":3239,"body":3240,"category":3223,"date":4078,"description":4079,"draft":3226,"extension":3227,"meta":4080,"navigation":463,"path":21,"seo":4081,"stem":4082,"tags":4083,"__hash__":4085},"blog\u002Fblog\u002Fbrowser-rendering-pipeline.md","浏览器是如何渲染页面的？",{"type":8,"value":3241,"toc":4058},[3242,3245,3260,3267,3274,3280,3284,3411,3414,3427,3437,3441,3451,3465,3471,3474,3498,3502,3516,3520,3536,3559,3565,3657,3661,3668,3671,3697,3712,3716,3726,3730,3737,3798,3804,3817,3821,3827,3834,3837,3862,3868,3872,3889,3895,3904,3916,3920,3923,3927,3938,3942,3959,3963,3977,3983,3990,3994,4003,4005,4055],[11,3243,3239],{"id":3244},"浏览器是如何渲染页面的",[15,3246,3247,3248,3250,3251,3254,3255,3259],{},"当网络进程把 HTML 文档下载完成后，渲染进程会收到字节流，并往 ",[25,3249,322],{}," 的消息队列里塞入一个 ",[25,3252,3253],{},"渲染任务","。主线程在 ",[19,3256,3258],{"href":3257},"\u002Fblog\u002Fjs-event-loop","事件循环"," 里取出该任务后，才开始「把文档变成屏幕上的像素」。",[15,3261,3262],{},[3263,3264],"img",{"alt":3265,"src":3266},"HTML 下载完成后渲染任务进入消息队列","\u002Fdemos\u002Fbrowser-rendering\u002Ftask-entry.svg",[15,3268,3269,3270,3273],{},"这不是一次性的函数调用，而是一条 ",[25,3271,3272],{},"流水线","：每个阶段有明确的输入和输出，上一阶段的产出是下一阶段的输入。可以把它想象成工厂里的装配线——材料从一端进去，成品从另一端出来。",[15,3275,3276],{},[3263,3277],{"alt":3278,"src":3279},"渲染流水线总览：主线程五段 + 合成三段","\u002Fdemos\u002Fbrowser-rendering\u002Fpipeline-overview.svg",[38,3281,3283],{"id":3282},"总览八个阶段","总览：八个阶段",[234,3285,3286,3302],{},[237,3287,3288],{},[240,3289,3290,3293,3296,3299],{},[243,3291,3292],{},"阶段",[243,3294,3295],{},"执行者",[243,3297,3298],{},"输入",[243,3300,3301],{},"输出",[253,3303,3304,3317,3330,3343,3356,3369,3383,3397],{},[240,3305,3306,3309,3311,3314],{},[258,3307,3308],{},"HTML 解析",[258,3310,322],{},[258,3312,3313],{},"HTML 字节流",[258,3315,3316],{},"DOM 树 + CSSOM 树",[240,3318,3319,3322,3324,3327],{},[258,3320,3321],{},"样式计算",[258,3323,322],{},[258,3325,3326],{},"DOM + CSSOM",[258,3328,3329],{},"带 Computed Style 的节点",[240,3331,3332,3335,3337,3340],{},[258,3333,3334],{},"布局 Layout",[258,3336,322],{},[258,3338,3339],{},"样式树",[258,3341,3342],{},"布局树（几何信息）",[240,3344,3345,3348,3350,3353],{},[258,3346,3347],{},"分层 Layer",[258,3349,322],{},[258,3351,3352],{},"布局树",[258,3354,3355],{},"图层树",[240,3357,3358,3361,3363,3366],{},[258,3359,3360],{},"绘制 Paint",[258,3362,322],{},[258,3364,3365],{},"图层",[258,3367,3368],{},"绘制指令集（display list）",[240,3370,3371,3374,3377,3380],{},[258,3372,3373],{},"分块 Tiling",[258,3375,3376],{},"合成线程",[258,3378,3379],{},"绘制指令",[258,3381,3382],{},"图层小块",[240,3384,3385,3388,3391,3394],{},[258,3386,3387],{},"光栅化 Raster",[258,3389,3390],{},"GPU 进程",[258,3392,3393],{},"块信息",[258,3395,3396],{},"位图（bitmap）",[240,3398,3399,3402,3405,3408],{},[258,3400,3401],{},"画 Draw",[258,3403,3404],{},"合成线程 + GPU",[258,3406,3407],{},"位图",[258,3409,3410],{},"屏幕像素",[15,3412,3413],{},"记忆口诀：",[312,3415,3416,3422],{},[315,3417,3418,3421],{},[25,3419,3420],{},"主线程","：Parse → Style → Layout → Layer → Paint",[315,3423,3424,3426],{},[25,3425,3404],{},"：Tiling → Raster → Draw",[15,3428,3429,3430,3432,3433,3436],{},"主线程极其繁忙——",[19,3431,3258],{"href":3257}," 里还要执行 JS、处理事件、跑计时器。渲染只是它众多职责之一；一旦某段 JS 长时间占用主线程，整条渲染流水线都会暂停（见 ",[19,3434,3435],{"href":3229},"reflow、repaint 与 transform","）。",[38,3438,3440],{"id":3439},"第一步html-解析","第一步：HTML 解析",[15,3442,3443,3444,3447,3448,3193],{},"解析 HTML 时，遇到 ",[25,3445,3446],{},"CSS 就解析 CSS","，遇到 ",[25,3449,3450],{},"JS 就执行 JS",[15,3452,3453,3454,3457,3458,32,3461,3464],{},"为提高效率，浏览器在正式解析前还会启动 ",[25,3455,3456],{},"预解析（preload scanner）","，提前下载 HTML 里 ",[83,3459,3460],{},"\u003Clink>",[83,3462,3463],{},"\u003Cscript>"," 引用的外部资源，减少主线程空等。",[15,3466,3467],{},[3263,3468],{"alt":3469,"src":3470},"解析 HTML 得到 DOM 树与 CSSOM 树","\u002Fdemos\u002Fbrowser-rendering\u002Fparse-dom-cssom.svg",[15,3472,3473],{},"解析完成后得到两棵树：",[312,3475,3476,3482],{},[315,3477,3478,3481],{},[25,3479,3480],{},"DOM 树","：文档结构，对应 HTML 标签的嵌套关系",[315,3483,3484,3487,3488,32,3491,3494,3495,3497],{},[25,3485,3486],{},"CSSOM 树","：汇总 ",[25,3489,3490],{},"用户代理默认样式",[83,3492,3493],{},"\u003Cstyle>","、外链 CSS、行内 ",[83,3496,1908],{}," 等全部 CSS 规则",[55,3499,3501],{"id":3500},"css-为什么不阻塞-html-解析","CSS 为什么不阻塞 HTML 解析？",[15,3503,3504,3505,3508,3509,3512,3513,3193],{},"主线程扫到 ",[83,3506,3507],{},"\u003Clink rel=\"stylesheet\">"," 时，即使 CSS 文件还没下载完，",[25,3510,3511],{},"也不会停下来等","，会继续解析后面的 HTML。CSS 的下载与解析由预解析等机制并行处理，因此 ",[25,3514,3515],{},"CSS 默认不阻塞 DOM 树的构建",[55,3517,3519],{"id":3518},"js-为什么会阻塞-html-解析","JS 为什么会阻塞 HTML 解析？",[15,3521,3504,3522,81,3525,3527,3528,3531,3532,3535],{},[25,3523,3524],{},"同步",[83,3526,3463],{}," 时，会 ",[25,3529,3530],{},"暂停 HTML 解析","，先等 JS 下载并执行完。因为 JS 可能通过 ",[83,3533,3534],{},"document.write"," 等方式修改 DOM，必须保证 DOM 生成顺序与脚本执行顺序一致。",[15,3537,3538,32,3541,32,3544,3547,3548,3551,3552,3554,3555,3558],{},[83,3539,3540],{},"defer",[83,3542,3543],{},"async",[83,3545,3546],{},"type=\"module\""," 等会改变具体行为，但 ",[25,3549,3550],{},"「同步脚本阻塞解析」"," 是默认规则，也是 ",[83,3553,3463],{}," 放 ",[83,3556,3557],{},"\u003C\u002Fbody>"," 前、或把脚本移到底部的原因。",[15,3560,3561],{},[3263,3562],{"alt":3563,"src":3564},"link 不阻塞解析，同步 script 会阻塞","\u002Fdemos\u002Fbrowser-rendering\u002Fcss-js-parse.svg",[101,3566,3569],{"lang":3567,"title":3568},"html","同步脚本阻塞 DOM 构建",[106,3570,3573],{"className":3571,"code":3572,"language":3567,"meta":110,"style":110},"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",[83,3574,3575,3596,3604,3624,3632,3652],{"__ignoreMap":110},[114,3576,3577,3579,3581,3584,3586,3589,3592,3594],{"class":116,"line":117},[114,3578,376],{"class":131},[114,3580,15],{"class":379},[114,3582,3583],{"class":135}," id",[114,3585,155],{"class":131},[114,3587,3588],{"class":142},"\"before\"",[114,3590,3591],{"class":131},">before\u003C\u002F",[114,3593,15],{"class":379},[114,3595,383],{"class":131},[114,3597,3598,3600,3602],{"class":116,"line":149},[114,3599,376],{"class":131},[114,3601,968],{"class":379},[114,3603,383],{"class":131},[114,3605,3606,3609,3611,3613,3616,3619,3621],{"class":116,"line":161},[114,3607,3608],{"class":131},"  document.",[114,3610,136],{"class":135},[114,3612,139],{"class":131},[114,3614,3615],{"class":142},"'#before'",[114,3617,3618],{"class":131},").textContent ",[114,3620,155],{"class":120},[114,3622,3623],{"class":142}," 'changed'\n",[114,3625,3626,3628,3630],{"class":116,"line":168},[114,3627,951],{"class":131},[114,3629,968],{"class":379},[114,3631,383],{"class":131},[114,3633,3634,3636,3638,3640,3642,3645,3648,3650],{"class":116,"line":180},[114,3635,376],{"class":131},[114,3637,15],{"class":379},[114,3639,3583],{"class":135},[114,3641,155],{"class":131},[114,3643,3644],{"class":142},"\"after\"",[114,3646,3647],{"class":131},">after\u003C\u002F",[114,3649,15],{"class":379},[114,3651,383],{"class":131},[114,3653,3654],{"class":116,"line":450},[114,3655,3656],{"class":164},"\u003C!-- 解析器在遇到 script 时必须先执行完再继续 -->\n",[38,3658,3660],{"id":3659},"第二步样式计算","第二步：样式计算",[15,3662,3663,3664,3667],{},"主线程遍历 DOM，结合 CSSOM，为每个节点算出最终用于渲染的样式，即 ",[25,3665,3666],{},"Computed Style","（计算样式）。",[15,3669,3670],{},"此阶段会把许多值「算实」：",[312,3672,3673,3682,3689],{},[315,3674,3675,3678,3679],{},[83,3676,3677],{},"red"," → ",[83,3680,3681],{},"rgb(255, 0, 0)",[315,3683,3684,3678,3686,3688],{},[83,3685,2070],{},[83,3687,1936],{},"（在父级字号已知时）",[315,3690,3691,3692,3696],{},"经过 ",[19,3693,3695],{"href":3694},"\u002Fblog\u002Fcss-property-calculation","层叠、继承、默认值"," 得到每个属性的最终声明",[15,3698,220,3699,3702,3703,3707,3708,3711],{},[25,3700,3701],{},"百分比宽高"," 等仍可能待定——它们依赖 ",[19,3704,3706],{"href":3705},"\u002Fblog\u002Fcss-containing-block","包含块","，要在 ",[25,3709,3710],{},"布局"," 阶段结合几何上下文才能定稿。",[38,3713,3715],{"id":3714},"第三步布局layout","第三步：布局（Layout）",[15,3717,3718,3719,3722,3723,3193],{},"布局阶段遍历带样式的节点，计算 ",[25,3720,3721],{},"几何信息","：宽高、位置（相对包含块）、行盒高度等，产出 ",[25,3724,3725],{},"布局树（Layout Tree）",[55,3727,3729],{"id":3728},"布局树-dom-树","布局树 ≠ DOM 树",[15,3731,3732,3733,3736],{},"两棵树 ",[25,3734,3735],{},"常常不是一一对应","：",[234,3738,3739,3750],{},[237,3740,3741],{},[240,3742,3743,3746,3748],{},[243,3744,3745],{},"情况",[243,3747,3480],{},[243,3749,3352],{},[253,3751,3752,3768,3787],{},[240,3753,3754,3759,3762],{},[258,3755,3756],{},[83,3757,3758],{},"display: none",[258,3760,3761],{},"有节点",[258,3763,3764,3765,3352],{},"无几何信息，",[25,3766,3767],{},"不进",[240,3769,3770,3778,3781],{},[258,3771,3772,337,3775],{},[83,3773,3774],{},"::before",[83,3776,3777],{},"::after",[258,3779,3780],{},"无对应节点",[258,3782,3783,3784,3352],{},"有几何信息，",[25,3785,3786],{},"会进",[240,3788,3789,3792,3795],{},[258,3790,3791],{},"匿名行盒 \u002F 块盒",[258,3793,3794],{},"无独立节点",[258,3796,3797],{},"布局算法自动生成",[15,3799,3800],{},[3263,3801],{"alt":3802,"src":3803},"DOM 树与布局树并不总是一一对应","\u002Fdemos\u002Fbrowser-rendering\u002Fdom-vs-layout.svg",[15,3805,3806,3807,3809,3810,3813,3814,3193],{},"布局完成后，每个可见元素在屏幕上「占哪块地方」就确定了。改宽高、边距、",[83,3808,271],{}," 等会触发 ",[25,3811,3812],{},"reflow（回流）","，需要重新跑 Layout 及之后阶段——详见 ",[19,3815,3816],{"href":3229},"reflow 与 repaint",[38,3818,3820],{"id":3819},"第四步分层layer","第四步：分层（Layer）",[15,3822,3823,3824,3193],{},"主线程按策略把布局树 ",[25,3825,3826],{},"分成多个图层（Layer）",[15,3828,3829,3830,3833],{},"分层是性能优化：将来只改某一层的样式或内容时，可以 ",[25,3831,3832],{},"只更新该层"," 的后续步骤，而不必重算整页。",[15,3835,3836],{},"常见影响因素：",[312,3838,3839,3842,3849,3856],{},[315,3840,3841],{},"滚动容器、滚动条",[315,3843,3844,3845,3848],{},"堆叠上下文（",[83,3846,3847],{},"z-index"," 等）",[315,3850,3851,32,3853],{},[83,3852,296],{},[83,3854,3855],{},"opacity",[315,3857,3858,3861],{},[83,3859,3860],{},"will-change"," 提示浏览器提前分层",[15,3863,3864,3865,3193],{},"分层策略因浏览器而异，属于实现细节；开发上只需知道：",[25,3866,3867],{},"分层是为了增量更新更快",[38,3869,3871],{"id":3870},"第五步绘制paint","第五步：绘制（Paint）",[15,3873,3874,3875,3878,3879,3881,3882,32,3885,3888],{},"主线程为 ",[25,3876,3877],{},"每一层"," 单独生成 ",[25,3880,3368],{},"，描述这一层该怎么画——类似 Canvas 的 ",[83,3883,3884],{},"fillRect",[83,3886,3887],{},"drawText"," 等调用序列。",[15,3890,3891],{},[3263,3892],{"alt":3893,"src":3894},"图层与绘制指令","\u002Fdemos\u002Fbrowser-rendering\u002Flayers-paint.svg",[15,3896,3897,3900,3901,3193],{},[25,3898,3899],{},"渲染主线程的工作到这里为止。"," 之后把各层的绘制信息交给 ",[25,3902,3903],{},"合成线程（Compositor Thread）",[15,3905,3906,3907,32,3909,3911,3912,3915],{},"改 ",[83,3908,206],{},[83,3910,209],{}," 等可见但不改布局的样式，可能只需 ",[25,3913,3914],{},"repaint","（重算绘制及之后），不必完整 reflow。",[38,3917,3919],{"id":3918},"第六八步分块光栅化上屏","第六～八步：分块、光栅化、上屏",[15,3921,3922],{},"合成线程接手后：",[55,3924,3926],{"id":3925},"_1-分块tiling","1. 分块（Tiling）",[15,3928,3929,3930,3933,3934,3937],{},"把每一层划成更小的 ",[25,3931,3932],{},"图块（tile）","，交给 ",[25,3935,3936],{},"线程池"," 并行处理，避免单次任务过大。",[55,3939,3941],{"id":3940},"_2-光栅化raster","2. 光栅化（Raster）",[15,3943,3944,3945,3947,3948,3950,3951,3954,3955,3958],{},"把块信息交给 ",[25,3946,3390],{},"，由 GPU 以极高速度转成 ",[25,3949,3396],{},"——每个块是一小块像素矩阵。靠近 ",[25,3952,3953],{},"视口"," 的块会 ",[25,3956,3957],{},"优先"," 光栅化，所以你滚动时远处内容可能稍晚才清晰。",[55,3960,3962],{"id":3961},"_3-画draw","3. 画（Draw）",[15,3964,3965,3966,3969,3970,3972,3973,3976],{},"合成线程为每块位图生成 ",[25,3967,3968],{},"指引（quad）","：在屏幕上的位置、旋转、缩放等。",[83,3971,296],{}," 的变形主要发生在此阶段，",[25,3974,3975],{},"不经过主线程的 Layout\u002FPaint","，因此动画往往更顺滑。",[15,3978,3979],{},[3263,3980],{"alt":3981,"src":3982},"分块、光栅化、quad、GPU 上屏","\u002Fdemos\u002Fbrowser-rendering\u002Fcompositor-gpu.svg",[15,3984,3985,3986,3989],{},"最后 GPU 进程通过系统调用把结果交给显卡。",[25,3987,3988],{},"渲染进程跑在沙盒里","，不能直接碰硬件，崩溃也不会拖垮整个系统。",[38,3991,3993],{"id":3992},"常见面试答法精简版","常见面试答法（精简版）",[226,3995,3996],{},[15,3997,3998,3999,4002],{},"网络进程收到 HTML 后，产生渲染任务进入渲染主线程的消息队列；事件循环取出任务，开启流水线：",[25,4000,4001],{},"解析 → 样式 → 布局 → 分层 → 绘制 → 分块 → 光栅化 → 画","。主线程负责前五步，合成线程和 GPU 负责后三步。CSS 不阻塞 DOM 构建，同步 JS 会阻塞解析。改动样式可能触发 reflow\u002Frepaint，是流水线运行中的局部重跑。",[38,4004,3182],{"id":3182},[312,4006,4007,4014,4019,4028,4043],{},[315,4008,4009,4010,4013],{},"渲染由 ",[25,4011,4012],{},"事件循环调度","，是主线程与合成线程协作的流水线。",[315,4015,4016,3193],{},[25,4017,4018],{},"DOM + CSSOM → 样式 → 布局 → 图层 → 绘制指令 → 位图 → 屏幕",[315,4020,4021,4024,4025,3193],{},[25,4022,4023],{},"同步 JS 阻塞解析","；",[25,4026,4027],{},"CSS 默认不阻塞 DOM",[315,4029,4030,4033,4034,4033,4037,4024,4040,4042],{},[25,4031,4032],{},"Layout 定几何","，",[25,4035,4036],{},"Paint 出指令",[25,4038,4039],{},"Compositor + GPU 上屏",[83,4041,296],{}," 多在合成阶段完成。",[315,4044,4045,4046,32,4049,32,4051,4054],{},"与 ",[19,4047,4048],{"href":3694},"CSS 属性计算",[19,4050,3706],{"href":3705},[19,4052,4053],{"href":3229},"reflow\u002Frepaint"," 串联起来，就是前端性能优化的完整地图。",[1908,4056,4057],{},"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":110,"searchDepth":161,"depth":161,"links":4059},[4060,4061,4065,4066,4069,4070,4071,4076,4077],{"id":3282,"depth":149,"text":3283},{"id":3439,"depth":149,"text":3440,"children":4062},[4063,4064],{"id":3500,"depth":161,"text":3501},{"id":3518,"depth":161,"text":3519},{"id":3659,"depth":149,"text":3660},{"id":3714,"depth":149,"text":3715,"children":4067},[4068],{"id":3728,"depth":161,"text":3729},{"id":3819,"depth":149,"text":3820},{"id":3870,"depth":149,"text":3871},{"id":3918,"depth":149,"text":3919,"children":4072},[4073,4074,4075],{"id":3925,"depth":161,"text":3926},{"id":3940,"depth":161,"text":3941},{"id":3961,"depth":161,"text":3962},{"id":3992,"depth":149,"text":3993},{"id":3182,"depth":149,"text":3182},"2026-06-25","从 HTML 解析到 GPU 上屏，详解渲染主线程与合成线程分工，以及 DOM 树、布局树、图层与位图如何串联成流水线。",{},{"title":3239,"description":4079},"blog\u002Fbrowser-rendering-pipeline",[3233,3234,4084],"interview","4ob-xZgG6ctKPWs2Yp8VJvQBEfU9jF8aTlNCAjPdHBg",{"id":4087,"title":4088,"body":4089,"category":3223,"date":4334,"description":4335,"draft":3226,"extension":3227,"meta":4336,"navigation":463,"path":4337,"seo":4338,"stem":4339,"tags":4340,"__hash__":4344},"blog\u002Fblog\u002Finfinite-parallax-scroll.md","无限视差全屏滚动",{"type":8,"value":4090,"toc":4324},[4091,4093,4108,4111,4115,4128,4136,4157,4161,4190,4194,4205,4274,4278,4292,4295,4302,4305,4307],[11,4092,4088],{"id":4088},[15,4094,4095,4096,4099,4100,4103,4104,4107],{},"全屏图片轮播不一定要用 Swiper。把原生滚动禁掉，自己接管 ",[83,4097,4098],{},"wheel"," 和 ",[83,4101,4102],{},"touch","，用 ",[25,4105,4106],{},"prev \u002F cur \u002F next 三个 DOM 窗口","循环展示图片，配合遮罩高度动画和视差位移，就能做出无限滚动的切屏效果。下方 demo 可滚轮或滑动切换。",[38,4109,4110],{"id":4110},"核心链路",[55,4112,4114],{"id":4113},"_1-禁原生滚动统一输入","1. 禁原生滚动，统一输入",[15,4116,4117,4120,4121,4124,4125,3736],{},[83,4118,4119],{},"html, body { overflow: hidden }","（全屏页）或容器 ",[83,4122,4123],{},"touch-action: none","，所有滚动意图都转成 ",[83,4126,4127],{},"pixelDelta",[106,4129,4134],{"className":4130,"code":4132,"language":4133},[4131],"language-text","wheel \u002F touch → applyScrollDelta → targetProgress ± step\n                                      ↓\n                              rAF lerp → progress → render()\n","text",[83,4135,4132],{"__ignoreMap":110},[15,4137,4138,4099,4140,81,4142,4148,4149,4152,4153,4156],{},[83,4139,4098],{},[83,4141,4102],{},[25,4143,4144,4145],{},"只改 ",[83,4146,4147],{},"targetProgress","，不直接改 ",[83,4150,4151],{},"progress","，中间用 ",[83,4154,4155],{},"LERP"," 平滑追赶，手感更跟手。",[55,4158,4160],{"id":4159},"_2-三屏窗口-循环索引","2. 三屏窗口 + 循环索引",[15,4162,4163,4164,32,4167,32,4170,4173,4174,4177,4178,4181,4182,4185,4186,4189],{},"DOM 里始终只有三张 slide：",[83,4165,4166],{},"prev",[83,4168,4169],{},"cur",[83,4171,4172],{},"next","。当前 ",[83,4175,4176],{},"index"," 指向居中那张；切屏完成后 ",[83,4179,4180],{},"index ± 1","，重新 ",[83,4183,4184],{},"bindImages"," 绑定 ",[83,4187,4188],{},"(index-1, index, index+1)"," 取模后的图片 URL。图片列表再长，DOM 节点数也不变。",[55,4191,4193],{"id":4192},"_3-mask-高度实现切屏","3. mask 高度实现切屏",[15,4195,4196,4197,4200,4201,4204],{},"每张 slide 内有一层 ",[83,4198,4199],{},"slide__mask","，通过改 ",[83,4202,4203],{},"height"," 控制图片可见区域：",[234,4206,4207,4220],{},[237,4208,4209],{},[240,4210,4211,4214,4217],{},[243,4212,4213],{},"方向",[243,4215,4216],{},"动画",[243,4218,4219],{},"mask 变化",[253,4221,4222,4244,4263],{},[240,4223,4224,4227,4232],{},[258,4225,4226],{},"向下滚",[258,4228,4229],{},[83,4230,4231],{},"scroll-down",[258,4233,4234,4236,4237,3678,4240,4243],{},[83,4235,4172],{}," 的 mask 从 ",[83,4238,4239],{},"0%",[83,4241,4242],{},"100%","，新图从上落下",[240,4245,4246,4249,4254],{},[258,4247,4248],{},"向上滚",[258,4250,4251],{},[83,4252,4253],{},"scroll-up",[258,4255,4256,4236,4258,3678,4260,4262],{},[83,4257,4169],{},[83,4259,4242],{},[83,4261,4239],{},"，当前图收起露出上一张",[240,4264,4265,4268,4271],{},[258,4266,4267],{},"静止",[258,4269,4270],{},"idle",[258,4272,4273],{},"prev\u002Fcur 全显，next 隐藏",[55,4275,4277],{"id":4276},"_4-视差位移","4. 视差位移",[15,4279,4280,4281,4284,4285,4287,4288,4291],{},"新图落下前，图片先 ",[83,4282,4283],{},"translateY(PARALLAX_SHIFT%)"," 偏上一点；",[83,4286,4151],{}," 从 0→1 时逐渐回到 0。绝对值越大，下落感越强。位移由 JS 每帧写入，不用 CSS ",[83,4289,4290],{},"transition","，和 lerp 进度同步。",[38,4293,4294],{"id":4294},"在线体验",[15,4296,4297,4298,3193],{},"在 demo 区域内滚轮或上下滑动切换图片。全屏体验可打开 ",[19,4299,4301],{"href":4300},"\u002Fdemos\u002Fparallax-scroll","独立预览页",[4303,4304],"parallax-scroll-demo",{},[38,4306,3182],{"id":3182},[15,4308,4309,4310,4313,4314,4317,4318,4099,4321,4323],{},"无限视差滚动的本质是 ",[25,4311,4312],{},"有限 DOM + 循环索引 + 自定义进度","。难点不在图片数量，而在输入统一（wheel 行\u002F页模式、触控板、触摸）、进度可逆（滚到一半往回滚能取消）、以及 ",[83,4315,4316],{},"prefers-reduced-motion"," 时关掉 lerp 拖尾。如果你要做类似效果，建议先把三屏窗口和 mask 动画跑通，再调 ",[83,4319,4320],{},"SCROLL_THRESHOLD",[83,4322,4155],{}," 找手感。",{"title":110,"searchDepth":161,"depth":161,"links":4325},[4326,4332,4333],{"id":4110,"depth":149,"text":4110,"children":4327},[4328,4329,4330,4331],{"id":4113,"depth":161,"text":4114},{"id":4159,"depth":161,"text":4160},{"id":4192,"depth":161,"text":4193},{"id":4276,"depth":161,"text":4277},{"id":4294,"depth":149,"text":4294},{"id":3182,"depth":149,"text":3182},"2026-06-15","禁用原生滚动，用 wheel 与 touch 驱动 prev\u002Fcur\u002Fnext 三屏窗口，mask 高度动画与 rAF lerp 实现无限视差切屏。",{},"\u002Fblog\u002Finfinite-parallax-scroll",{"title":4088,"description":4335},"blog\u002Finfinite-parallax-scroll",[4341,4342,4343,4102],"scroll","parallax","animation","HTL-V7yHnEHTcmos1lrSu5suLz2IHx5hmawb99kBb-0",{"id":4346,"title":4347,"body":4348,"category":3223,"date":4681,"description":4682,"draft":3226,"extension":3227,"meta":4683,"navigation":463,"path":4684,"seo":4685,"stem":4686,"tags":4687,"__hash__":4691},"blog\u002Fblog\u002Fcanvas-random-tree.md","Canvas 递归绘制随机分形树",{"type":8,"value":4349,"toc":4671},[4350,4353,4356,4358,4362,4368,4394,4401,4405,4420,4544,4548,4562,4623,4627,4642,4644,4652,4655,4657,4668],[11,4351,4347],{"id":4352},"canvas-递归绘制随机分形树",[15,4354,4355],{},"分形树是 Canvas 入门的经典练习：从树干出发，每画一节枝条就递归分叉，直到深度耗尽。加上伪随机种子，每次点击都能生成形态各异但风格统一的树。下面 demo 支持点击换树、窗口缩放高清重绘。",[38,4357,4110],{"id":4110},[55,4359,4361],{"id":4360},"_1-递归分形depth-驱动一切","1. 递归分形：depth 驱动一切",[15,4363,4364,4367],{},[83,4365,4366],{},"drawBranch(x, y, len, angle, depth, maxDepth)"," 是核心函数：",[4369,4370,4371,4378,4381,4388],"ol",{},[315,4372,4373,4374,4377],{},"画当前枝段（线宽由 ",[83,4375,4376],{},"depth \u002F maxDepth"," 的平方决定，主干粗、末梢细）",[315,4379,4380],{},"按深度决定分叉数量：主干区 2～3 叉，末梢 0～1 叉",[315,4382,4383,4384,4387],{},"子枝长度乘以 ",[83,4385,4386],{},"0.63～0.78"," 的随机缩放，逐层缩短",[315,4389,4390,4393],{},[83,4391,4392],{},"depth \u003C= 2"," 时在末梢稀疏开花",[15,4395,4396,4397,4400],{},"较粗的枝段还有概率从",[25,4398,4399],{},"线段中途","再分叉，让树形更自然。",[55,4402,4404],{"id":4403},"_2-角度约束树枝只朝上半平面","2. 角度约束：树枝只朝上半平面",[15,4406,4407,4408,4411,4412,4415,4416,4419],{},"Canvas 的 Y 轴向下，所以 ",[83,4409,4410],{},"sin(angle) \u003C= 0"," 表示朝上。",[83,4413,4414],{},"clampAngleUpward"," 把角度限制在 ",[83,4417,4418],{},"[-π, 0]","，避免枝条插进地面：",[106,4421,4423],{"className":108,"code":4422,"language":103,"meta":110,"style":110},"function clampAngleUpward(angle) {\n  let a = angle\n  while (a \u003C= -Math.PI) a += Math.PI * 2\n  while (a > Math.PI) a -= Math.PI * 2\n  if (a > 0) a = -a\n  return a\n}\n",[83,4424,4425,4439,4451,4487,4513,4532,4540],{"__ignoreMap":110},[114,4426,4427,4429,4432,4434,4437],{"class":116,"line":117},[114,4428,1153],{"class":120},[114,4430,4431],{"class":135}," clampAngleUpward",[114,4433,139],{"class":131},[114,4435,4436],{"class":1161},"angle",[114,4438,1165],{"class":131},[114,4440,4441,4444,4446,4448],{"class":116,"line":149},[114,4442,4443],{"class":120},"  let",[114,4445,1374],{"class":131},[114,4447,155],{"class":120},[114,4449,4450],{"class":131}," angle\n",[114,4452,4453,4456,4459,4462,4465,4468,4471,4474,4477,4479,4481,4484],{"class":116,"line":161},[114,4454,4455],{"class":120},"  while",[114,4457,4458],{"class":131}," (a ",[114,4460,4461],{"class":120},"\u003C=",[114,4463,4464],{"class":120}," -",[114,4466,4467],{"class":131},"Math.",[114,4469,4470],{"class":124},"PI",[114,4472,4473],{"class":131},") a ",[114,4475,4476],{"class":120},"+=",[114,4478,1224],{"class":131},[114,4480,4470],{"class":124},[114,4482,4483],{"class":120}," *",[114,4485,4486],{"class":124}," 2\n",[114,4488,4489,4491,4493,4496,4498,4500,4502,4505,4507,4509,4511],{"class":116,"line":168},[114,4490,4455],{"class":120},[114,4492,4458],{"class":131},[114,4494,4495],{"class":120},">",[114,4497,1224],{"class":131},[114,4499,4470],{"class":124},[114,4501,4473],{"class":131},[114,4503,4504],{"class":120},"-=",[114,4506,1224],{"class":131},[114,4508,4470],{"class":124},[114,4510,4483],{"class":120},[114,4512,4486],{"class":124},[114,4514,4515,4517,4519,4521,4523,4525,4527,4529],{"class":116,"line":180},[114,4516,1171],{"class":120},[114,4518,4458],{"class":131},[114,4520,4495],{"class":120},[114,4522,1988],{"class":124},[114,4524,4473],{"class":131},[114,4526,155],{"class":120},[114,4528,4464],{"class":120},[114,4530,4531],{"class":131},"a\n",[114,4533,4534,4537],{"class":116,"line":450},[114,4535,4536],{"class":120},"  return",[114,4538,4539],{"class":131}," a\n",[114,4541,4542],{"class":116,"line":460},[114,4543,1653],{"class":131},[55,4545,4547],{"id":4546},"_3-lcg-伪随机可复现的随机","3. LCG 伪随机：可复现的「随机」",[15,4549,4550,4551,4554,4555,4558,4559,4561],{},"用线性同余生成器（LCG）代替 ",[83,4552,4553],{},"Math.random()","，同一 ",[83,4556,4557],{},"seed"," 下每次绘制结果一致；点击画布时重置 ",[83,4560,4557],{},"，得到新树：",[106,4563,4565],{"className":108,"code":4564,"language":103,"meta":110,"style":110},"function rand() {\n  seed = (seed * 16807 + 0) % 2147483647\n  return (seed - 1) \u002F 2147483646\n}\n",[83,4566,4567,4576,4602,4619],{"__ignoreMap":110},[114,4568,4569,4571,4574],{"class":116,"line":117},[114,4570,1153],{"class":120},[114,4572,4573],{"class":135}," rand",[114,4575,1669],{"class":131},[114,4577,4578,4581,4583,4586,4588,4591,4593,4595,4597,4599],{"class":116,"line":149},[114,4579,4580],{"class":131},"  seed ",[114,4582,155],{"class":120},[114,4584,4585],{"class":131}," (seed ",[114,4587,1233],{"class":120},[114,4589,4590],{"class":124}," 16807",[114,4592,1239],{"class":120},[114,4594,1988],{"class":124},[114,4596,1368],{"class":131},[114,4598,2828],{"class":120},[114,4600,4601],{"class":124}," 2147483647\n",[114,4603,4604,4606,4608,4610,4612,4614,4616],{"class":116,"line":161},[114,4605,4536],{"class":120},[114,4607,4585],{"class":131},[114,4609,1202],{"class":120},[114,4611,2219],{"class":124},[114,4613,1368],{"class":131},[114,4615,1208],{"class":120},[114,4617,4618],{"class":124}," 2147483646\n",[114,4620,4621],{"class":116,"line":168},[114,4622,1653],{"class":131},[55,4624,4626],{"id":4625},"_4-devicepixelratio-高清适配","4. devicePixelRatio 高清适配",[15,4628,4629,4630,4633,4634,4637,4638,4641],{},"设置 ",[83,4631,4632],{},"canvas.width = cssWidth * dpr","，再用 ",[83,4635,4636],{},"ctx.setTransform(dpr, 0, 0, dpr, 0, 0)"," 缩放坐标系，Retina 屏上线条不会发虚。",[83,4639,4640],{},"resize"," 时按容器尺寸重算并整棵重绘。",[38,4643,4294],{"id":4294},[15,4645,4646,4647,4651],{},"点击下方画布可重新生成一棵树。也可 ",[19,4648,4650],{"href":4649},"\u002Fdemos\u002Frandom-tree","全屏预览"," 打开独立页面。",[4653,4654],"random-tree-demo",{},[38,4656,3182],{"id":3182},[15,4658,4659,4660,4663,4664,4667],{},"随机分形树的套路很固定：",[25,4661,4662],{},"递归 + 深度递减 + 随机参数","。难点在于细节——线宽平滑过渡、中途分叉、角度约束、末梢开花，这些让「算法树」看起来像「自然树」。如果你要做自己的生成艺术，可以从固定 ",[83,4665,4666],{},"maxDepth"," 和单一分叉规则开始，再逐步加随机性和装饰。",[1908,4669,4670],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}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":110,"searchDepth":161,"depth":161,"links":4672},[4673,4679,4680],{"id":4110,"depth":149,"text":4110,"children":4674},[4675,4676,4677,4678],{"id":4360,"depth":161,"text":4361},{"id":4403,"depth":161,"text":4404},{"id":4546,"depth":161,"text":4547},{"id":4625,"depth":161,"text":4626},{"id":4294,"depth":149,"text":4294},{"id":3182,"depth":149,"text":3182},"2026-06-10","用 Canvas 递归分形画随机树，LCG 伪随机保证可复现，点击换一棵，并适配高清屏 DPR。",{},"\u002Fblog\u002Fcanvas-random-tree",{"title":4347,"description":4682},"blog\u002Fcanvas-random-tree",[4688,4689,4690],"canvas","recursion","generative-art","aFdlBHe97GxQsBjnQGInjuLSEwD6EV7SRTsDTScAShA",1782309644793]