[{"data":1,"prerenderedAt":1209},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates":1200},{"id":4,"title":5,"body":6,"description":1179,"extension":1180,"meta":1181,"navigation":1193,"path":1194,"seo":1195,"stem":1198,"__hash__":1199},"content\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates\u002Findex.md","Reducing Presentation Delay from Large DOM Updates",{"type":7,"value":8,"toc":1164},"minimark",[9,14,34,37,180,185,231,235,241,247,253,259,391,395,400,403,662,665,669,748,756,760,794,797,801,816,819,901,905,916,1024,1028,1038,1056,1078,1082,1085,1089,1101,1114,1123,1127,1149,1154,1157,1160],[10,11,13],"h1",{"id":12},"how-to-reduce-presentation-delay-from-large-dom-updates","How to Reduce Presentation Delay from Large DOM Updates",[15,16,17,18,23,24,28,29,33],"p",{},"This guide addresses the rendering side of slow interactions, under ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F","Profiling Event Handlers for INP"," in ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement",". The handler is fast, the framework commit is reasonable, and yet the interaction takes ",[30,31,32],"code",{},"250ms"," because the browser spends 150ms recalculating styles, laying out and painting the result. That is presentation delay — the third INP phase — and it is governed by how much DOM changed and how much of the page that change invalidated.",[15,35,36],{},"The common culprits are interactions that swap large regions: switching tabs that each hold a full table, applying a filter that replaces a 300-card grid, expanding \"show all\" on a long list, or toggling a view mode that restyles every item. Each is a legitimate product feature; each can be rendered far more cheaply than the naive approach.",[15,38,39],{},[40,41,47,48,47,55,47,59,47,62,47,71,47,77,47,88,47,93,47,101,47,107,47,114,47,120,47,124,47,128,47,131,47,136,47,142,47,146,47,150,47,154,47,158,47,161,47,165,47,169,47,175,47],"svg",{"viewBox":42,"width":43,"role":44,"ariaLabel":45,"style":46},"0 0 760 188","100%","img","Timeline of a tab switch interaction where handler and commit are short but style, layout and paint of a 1,200-row table take most of the time.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[49,50],"rect",{"className":51,"x":53,"y":53,"width":43,"height":43,"fill":54},[52],"svg-canvas","0","#ffffff",[56,57,58],"title",{},"A tab switch dominated by rendering",[60,61,45],"desc",{},[49,63],{"x":64,"y":64,"width":65,"height":66,"rx":67,"fill":68,"stroke":69,"style":70},"1","758","186","10","none","currentColor","stroke-opacity:0.18",[72,73,58],"text",{"x":74,"y":75,"fill":69,"style":76},"28.0","34.0","font-size:16px;font-weight:700",[78,79],"line",{"x1":80,"y1":81,"x2":80,"y2":82,"stroke":83,"strokeWidth":84,"strokeDashArray":85,"style":87},"548.6","72.0","118.0","#b8860b","1.5",[86,86],"4","stroke-opacity:0.9",[72,89,92],{"x":74,"y":90,"fill":69,"style":91},"96.0","font-size:12.5px;font-weight:700","Interaction",[49,94],{"x":95,"y":96,"width":97,"height":98,"rx":99,"fill":69,"stroke":69,"style":100},"105.8","78.0","16.2","26.0","3","fill-opacity:0.06;stroke-opacity:0.4",[49,102],{"x":103,"y":96,"width":104,"height":98,"rx":99,"fill":105,"stroke":105,"style":106},"123.5","113.8","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[72,108,113],{"x":109,"y":110,"fill":69,"style":111,"textAnchor":112},"180.4","95.5","font-size:11.5px","middle","handler + commit",[49,115],{"x":116,"y":96,"width":117,"height":98,"rx":99,"fill":118,"stroke":83,"style":119},"238.8","198.1","#ffc300","fill-opacity:0.24;stroke-opacity:0.9",[72,121,123],{"x":122,"y":110,"fill":69,"style":111,"textAnchor":112},"337.9","style + layout",[49,125],{"x":126,"y":96,"width":127,"height":98,"rx":99,"fill":118,"stroke":83,"style":119},"438.4","220.3",[72,129,130],{"x":80,"y":110,"fill":69,"style":111,"textAnchor":112},"paint + raster",[78,132],{"x1":133,"y1":82,"x2":134,"y2":82,"stroke":69,"style":135},"105.0","726.0","stroke-opacity:0.4",[72,137,141],{"x":138,"y":139,"fill":140,"style":111,"textAnchor":112},"115.7","135.0","#51617a","0ms",[72,143,145],{"x":144,"y":139,"fill":140,"style":111,"textAnchor":112},"193.7","40ms",[72,147,149],{"x":148,"y":139,"fill":140,"style":111,"textAnchor":112},"282.5","80ms",[72,151,153],{"x":152,"y":139,"fill":140,"style":111,"textAnchor":112},"371.2","120ms",[72,155,157],{"x":156,"y":139,"fill":140,"style":111,"textAnchor":112},"459.9","160ms",[72,159,160],{"x":80,"y":139,"fill":140,"style":111,"textAnchor":112},"200ms",[72,162,164],{"x":163,"y":139,"fill":140,"style":111,"textAnchor":112},"637.3","240ms",[72,166,168],{"x":167,"y":139,"fill":140,"style":111,"textAnchor":112},"715.3","280ms",[72,170,174],{"x":80,"y":171,"fill":172,"style":173,"textAnchor":112},"68.0","#8a6608","font-size:12px;font-weight:700","INP 200ms",[72,176,179],{"x":74,"y":177,"fill":140,"style":178},"166.0","font-size:12.5px","Presentation delay is 190ms here — the scripts are not the problem.",[181,182,184],"h2",{"id":183},"rapid-diagnosis","Rapid Diagnosis",[186,187,188,204,210,219,225],"ul",{},[189,190,191,195,196,199,200,203],"li",{},[192,193,194],"strong",{},"Check the INP breakdown."," Presentation delay above ",[30,197,198],{},"50ms"," is worth attacking; above ",[30,201,202],{},"100ms"," it is usually the dominant problem.",[189,205,206,209],{},[192,207,208],{},"In the trace, measure the rendering tasks"," after the handler: Recalculate Style (with \"Elements affected\"), Layout (with node count and root), Paint and Commit.",[189,211,212,47,215,218],{},[192,213,214],{},"Count nodes inserted.",[30,216,217],{},"document.querySelectorAll('*').length"," before and after the interaction shows how much DOM the change added.",[189,220,221,224],{},[192,222,223],{},"Turn on Paint flashing."," If the whole viewport flashes for a change in one panel, invalidation is wider than necessary.",[189,226,227,230],{},[192,228,229],{},"Check for off-screen rendering."," If most inserted nodes are below the fold, you are paying to render content nobody can see yet.",[181,232,234],{"id":233},"root-cause-analysis","Root Cause Analysis",[15,236,237,240],{},[192,238,239],{},"1. Rendering everything at once."," The interaction inserts the complete new content — all 1,200 rows — in one commit. Rendering cost scales with node count.",[15,242,243,246],{},[192,244,245],{},"2. Off-screen content rendered eagerly."," Most of the inserted content is outside the viewport, yet it is styled, laid out and often painted before the next frame.",[15,248,249,252],{},[192,250,251],{},"3. Layout that is not contained."," The changed region's size affects the rest of the page, so layout runs from the document root rather than inside the region.",[15,254,255,258],{},[192,256,257],{},"4. Expensive styles on many elements."," Shadows, filters, gradients and complex selectors applied per item multiply paint and style cost by the number of items.",[15,260,261],{},[40,262,47,265,47,268,47,271,47,273,47,290,47,293,47,295,47,301,47,306,47,309,47,313,47,320,47,326,47,330,47,335,47,338,47,342,47,344,47,347,47,350,47,353,47,356,47,359,47,362,47,366,47,368,47,371,47,374,47,377,47,381,47,384,47,387,47],{"viewBox":263,"width":43,"role":44,"ariaLabel":264,"style":46},"0 0 760 325","Decision sequence for reducing presentation delay depending on whether the content is off-screen, list-like or independent of surrounding layout.",[49,266],{"className":267,"x":53,"y":53,"width":43,"height":43,"fill":54},[52],[56,269,270],{},"Choosing a technique for a large update",[60,272,264],{},[274,275,276],"defs",{},[277,278,285],"marker",{"id":279,"viewBox":280,"refX":281,"refY":282,"markerWidth":283,"markerHeight":283,"orient":284},"fa23f08b89","0 0 10 10","9","5","7","auto-start-reverse",[286,287],"path",{"d":288,"fill":69,"style":289},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[49,291],{"x":64,"y":64,"width":65,"height":292,"rx":67,"fill":68,"stroke":69,"style":70},"323",[72,294,270],{"x":74,"y":75,"fill":69,"style":76},[49,296],{"x":74,"y":297,"width":298,"height":299,"rx":300,"fill":69,"stroke":69,"style":100},"56.0","320.0","35.0","6",[72,302,305],{"x":303,"y":96,"fill":69,"style":304,"textAnchor":112},"188.0","font-size:13px;font-weight:700","Most new content below the fold?",[49,307],{"x":308,"y":297,"width":298,"height":299,"rx":300,"fill":105,"stroke":105,"style":106},"412.0",[72,310,312],{"x":311,"y":96,"fill":69,"style":178,"textAnchor":112},"572.0","content-visibility auto, or render the first screen first",[78,314],{"x1":315,"y1":316,"x2":317,"y2":316,"stroke":69,"strokeWidth":84,"style":318,"markerEnd":319},"348.0","73.5","410.0","stroke-opacity:0.6","url(#fa23f08b89)",[72,321,325],{"x":322,"y":323,"fill":105,"style":324,"textAnchor":112},"380.0","66.5","font-size:11.5px;font-weight:700","yes",[78,327],{"x1":303,"y1":328,"x2":303,"y2":329,"stroke":69,"strokeWidth":84,"style":318,"markerEnd":319},"91.0","125.0",[72,331,334],{"x":332,"y":333,"fill":140,"style":324},"196.0","113.0","no",[49,336],{"x":74,"y":337,"width":298,"height":299,"rx":300,"fill":69,"stroke":69,"style":100},"127.0",[72,339,341],{"x":303,"y":340,"fill":69,"style":304,"textAnchor":112},"149.0","Long list of similar items?",[49,343],{"x":308,"y":337,"width":298,"height":299,"rx":300,"fill":105,"stroke":105,"style":106},[72,345,346],{"x":311,"y":340,"fill":69,"style":178,"textAnchor":112},"Virtualise — render only visible rows",[78,348],{"x1":315,"y1":349,"x2":317,"y2":349,"stroke":69,"strokeWidth":84,"style":318,"markerEnd":319},"144.5",[72,351,325],{"x":322,"y":352,"fill":105,"style":324,"textAnchor":112},"137.5",[78,354],{"x1":303,"y1":355,"x2":303,"y2":332,"stroke":69,"strokeWidth":84,"style":318,"markerEnd":319},"162.0",[72,357,334],{"x":332,"y":358,"fill":140,"style":324},"184.0",[49,360],{"x":74,"y":361,"width":298,"height":299,"rx":300,"fill":69,"stroke":69,"style":100},"198.0",[72,363,365],{"x":303,"y":364,"fill":69,"style":304,"textAnchor":112},"220.0","Region size independent of the page?",[49,367],{"x":308,"y":361,"width":298,"height":299,"rx":300,"fill":105,"stroke":105,"style":106},[72,369,370],{"x":311,"y":364,"fill":69,"style":178,"textAnchor":112},"contain: layout paint on the region",[78,372],{"x1":315,"y1":373,"x2":317,"y2":373,"stroke":69,"strokeWidth":84,"style":318,"markerEnd":319},"215.5",[72,375,325],{"x":322,"y":376,"fill":105,"style":324,"textAnchor":112},"208.5",[78,378],{"x1":303,"y1":379,"x2":303,"y2":380,"stroke":69,"strokeWidth":84,"style":318,"markerEnd":319},"233.0","267.0",[72,382,334],{"x":332,"y":383,"fill":140,"style":324},"255.0",[49,385],{"x":74,"y":386,"width":298,"height":75,"rx":300,"fill":118,"stroke":83,"style":119},"269.0",[72,388,390],{"x":303,"y":389,"fill":69,"style":178,"textAnchor":112},"290.5","Reduce per-item style cost and node depth",[181,392,394],{"id":393},"step-by-step-resolution","Step-by-Step Resolution",[396,397,399],"h3",{"id":398},"_1-render-the-first-screenful-now-the-rest-after-paint","1. Render the first screenful now, the rest after paint",[15,401,402],{},"Split the update: commit what is visible immediately, then append the remainder in the next task, after the browser has painted the response to the interaction.",[404,405,410],"pre",{"className":406,"code":407,"language":408,"meta":409,"style":409},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","async function showTab(rows) {\n  const first = rows.slice(0, 40);\n  render(first);                                   \u002F\u002F visible immediately\n  await new Promise((r) => requestAnimationFrame(() => setTimeout(r, 0)));  \u002F\u002F after paint\n  for (let i = 40; i \u003C rows.length; i += 200) {\n    appendRows(rows.slice(i, i + 200));\n    await (globalThis.scheduler?.yield?.() ?? new Promise((r) => setTimeout(r, 0)));\n  }\n}\n\u002F\u002F trade-off: users who scroll immediately may reach the end of the first chunk\n\u002F\u002F before the rest arrives. Choose a first chunk larger than one viewport and\n\u002F\u002F keep scroll height stable with a placeholder of the final size.\n","javascript","",[30,411,412,438,470,483,529,571,593,632,638,644,650,656],{"__ignoreMap":409},[413,414,416,420,423,427,431,435],"span",{"class":78,"line":415},1,[413,417,419],{"class":418},"sPARh","async",[413,421,422],{"class":418}," function",[413,424,426],{"class":425},"smZ65"," showTab",[413,428,430],{"class":429},"saISM","(",[413,432,434],{"class":433},"sQw3B","rows",[413,436,437],{"class":429},") {\n",[413,439,441,444,448,451,454,457,459,461,464,467],{"class":78,"line":440},2,[413,442,443],{"class":418},"  const",[413,445,447],{"class":446},"sPXB4"," first",[413,449,450],{"class":418}," =",[413,452,453],{"class":429}," rows.",[413,455,456],{"class":425},"slice",[413,458,430],{"class":429},[413,460,53],{"class":446},[413,462,463],{"class":429},", ",[413,465,466],{"class":446},"40",[413,468,469],{"class":429},");\n",[413,471,473,476,479],{"class":78,"line":472},3,[413,474,475],{"class":425},"  render",[413,477,478],{"class":429},"(first);                                   ",[413,480,482],{"class":481},"sjfSM","\u002F\u002F visible immediately\n",[413,484,486,489,492,495,498,501,504,507,510,513,515,518,521,523,526],{"class":78,"line":485},4,[413,487,488],{"class":418},"  await",[413,490,491],{"class":418}," new",[413,493,494],{"class":446}," Promise",[413,496,497],{"class":429},"((",[413,499,500],{"class":433},"r",[413,502,503],{"class":429},") ",[413,505,506],{"class":418},"=>",[413,508,509],{"class":425}," requestAnimationFrame",[413,511,512],{"class":429},"(() ",[413,514,506],{"class":418},[413,516,517],{"class":425}," setTimeout",[413,519,520],{"class":429},"(r, ",[413,522,53],{"class":446},[413,524,525],{"class":429},")));  ",[413,527,528],{"class":481},"\u002F\u002F after paint\n",[413,530,532,535,538,541,544,547,550,553,556,558,561,563,566,569],{"class":78,"line":531},5,[413,533,534],{"class":418},"  for",[413,536,537],{"class":429}," (",[413,539,540],{"class":418},"let",[413,542,543],{"class":429}," i ",[413,545,546],{"class":418},"=",[413,548,549],{"class":446}," 40",[413,551,552],{"class":429},"; i ",[413,554,555],{"class":418},"\u003C",[413,557,453],{"class":429},[413,559,560],{"class":446},"length",[413,562,552],{"class":429},[413,564,565],{"class":418},"+=",[413,567,568],{"class":446}," 200",[413,570,437],{"class":429},[413,572,574,577,580,582,585,588,590],{"class":78,"line":573},6,[413,575,576],{"class":425},"    appendRows",[413,578,579],{"class":429},"(rows.",[413,581,456],{"class":425},[413,583,584],{"class":429},"(i, i ",[413,586,587],{"class":418},"+",[413,589,568],{"class":446},[413,591,592],{"class":429},"));\n",[413,594,596,599,602,605,608,611,613,615,617,619,621,623,625,627,629],{"class":78,"line":595},7,[413,597,598],{"class":418},"    await",[413,600,601],{"class":429}," (globalThis.scheduler?.",[413,603,604],{"class":425},"yield",[413,606,607],{"class":429},"?.() ",[413,609,610],{"class":418},"??",[413,612,491],{"class":418},[413,614,494],{"class":446},[413,616,497],{"class":429},[413,618,500],{"class":433},[413,620,503],{"class":429},[413,622,506],{"class":418},[413,624,517],{"class":425},[413,626,520],{"class":429},[413,628,53],{"class":446},[413,630,631],{"class":429},")));\n",[413,633,635],{"class":78,"line":634},8,[413,636,637],{"class":429},"  }\n",[413,639,641],{"class":78,"line":640},9,[413,642,643],{"class":429},"}\n",[413,645,647],{"class":78,"line":646},10,[413,648,649],{"class":481},"\u002F\u002F trade-off: users who scroll immediately may reach the end of the first chunk\n",[413,651,653],{"class":78,"line":652},11,[413,654,655],{"class":481},"\u002F\u002F before the rest arrives. Choose a first chunk larger than one viewport and\n",[413,657,659],{"class":78,"line":658},12,[413,660,661],{"class":481},"\u002F\u002F keep scroll height stable with a placeholder of the final size.\n",[15,663,664],{},"Expected outcome: the interaction's frame includes only the first chunk; presentation delay typically drops 60–80%.",[396,666,668],{"id":667},"_2-skip-rendering-off-screen-content-with-content-visibility","2. Skip rendering off-screen content with content-visibility",[404,670,674],{"className":671,"code":672,"language":673,"meta":409,"style":409},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast",".table-section, .card-group {\n  content-visibility: auto;\n  contain-intrinsic-size: auto 800px;     \u002F* estimated height until rendered *\u002F\n}\n\u002F* trade-off: off-screen sections are not rendered until near the viewport, so\n   in-page search (Ctrl+F) and anchor links still work, but scrolling into them\n   costs rendering at that moment. Avoid on content that is scrolled through\n   very quickly, such as a short page. *\u002F\n","css",[30,675,676,689,703,724,728,733,738,743],{"__ignoreMap":409},[413,677,678,681,683,686],{"class":78,"line":415},[413,679,680],{"class":446},".table-section",[413,682,463],{"class":429},[413,684,685],{"class":446},".card-group",[413,687,688],{"class":429}," {\n",[413,690,691,694,697,700],{"class":78,"line":440},[413,692,693],{"class":446},"  content-visibility",[413,695,696],{"class":429},": ",[413,698,699],{"class":446},"auto",[413,701,702],{"class":429},";\n",[413,704,705,708,710,712,715,718,721],{"class":78,"line":472},[413,706,707],{"class":446},"  contain-intrinsic-size",[413,709,696],{"class":429},[413,711,699],{"class":446},[413,713,714],{"class":446}," 800",[413,716,717],{"class":418},"px",[413,719,720],{"class":429},";     ",[413,722,723],{"class":481},"\u002F* estimated height until rendered *\u002F\n",[413,725,726],{"class":78,"line":485},[413,727,643],{"class":429},[413,729,730],{"class":78,"line":531},[413,731,732],{"class":481},"\u002F* trade-off: off-screen sections are not rendered until near the viewport, so\n",[413,734,735],{"class":78,"line":573},[413,736,737],{"class":481},"   in-page search (Ctrl+F) and anchor links still work, but scrolling into them\n",[413,739,740],{"class":78,"line":595},[413,741,742],{"class":481},"   costs rendering at that moment. Avoid on content that is scrolled through\n",[413,744,745],{"class":78,"line":634},[413,746,747],{"class":481},"   very quickly, such as a short page. *\u002F\n",[15,749,750,751,755],{},"Expected outcome: style and layout cost scales with visible content rather than total content. Details in ",[19,752,754],{"href":753},"\u002Frendering-css-performance\u002Fcss-containment-and-content-visibility\u002Fusing-content-visibility-auto-on-long-pages\u002F","using content-visibility auto on long pages",".",[396,757,759],{"id":758},"_3-contain-the-changed-region","3. Contain the changed region",[404,761,763],{"className":671,"code":762,"language":673,"meta":409,"style":409},".tab-panel { contain: layout paint; }\n\u002F* trade-off: paint containment clips descendants to the panel box — tooltips\n   or dropdowns that overflow the panel will be cut off. Render those in a\n   portal at the body level. *\u002F\n",[30,764,765,779,784,789],{"__ignoreMap":409},[413,766,767,770,773,776],{"class":78,"line":415},[413,768,769],{"class":446},".tab-panel",[413,771,772],{"class":429}," { ",[413,774,775],{"class":446},"contain",[413,777,778],{"class":429},": layout paint; }\n",[413,780,781],{"class":78,"line":440},[413,782,783],{"class":481},"\u002F* trade-off: paint containment clips descendants to the panel box — tooltips\n",[413,785,786],{"class":78,"line":472},[413,787,788],{"class":481},"   or dropdowns that overflow the panel will be cut off. Render those in a\n",[413,790,791],{"class":78,"line":485},[413,792,793],{"class":481},"   portal at the body level. *\u002F\n",[15,795,796],{},"Expected outcome: Layout's root becomes the panel instead of the document, and paint is limited to the panel area.",[396,798,800],{"id":799},"_4-make-each-item-cheaper-to-render","4. Make each item cheaper to render",[15,802,803,804,807,808,811,812,815],{},"Flatten wrapper divs, replace per-item ",[30,805,806],{},"box-shadow"," and ",[30,809,810],{},"filter"," with simpler borders or a shared background, and avoid selectors like ",[30,813,814],{},".grid > * + *"," that match across the whole list.",[15,817,818],{},"Expected outcome: per-item rendering cost falls; on dense grids this compounds into tens of milliseconds.",[15,820,821],{},[40,822,47,825,47,828,47,831,47,833,47,836,47,838,47,843,47,850,47,856,47,861,47,865,47,869,47,873,47,877,47,882,47,886,47,890,47,894,47,898,47],{"viewBox":823,"width":43,"role":44,"ariaLabel":824,"style":46},"0 0 760 216","Bar chart of presentation delay before and after each technique is applied cumulatively.",[49,826],{"className":827,"x":53,"y":53,"width":43,"height":43,"fill":54},[52],[56,829,830],{},"Presentation delay for a 1,200-row tab switch",[60,832,824],{},[49,834],{"x":64,"y":64,"width":65,"height":835,"rx":67,"fill":68,"stroke":69,"style":70},"214",[72,837,830],{"x":74,"y":75,"fill":69,"style":76},[78,839],{"x1":840,"y1":81,"x2":840,"y2":841,"stroke":83,"strokeWidth":84,"strokeDashArray":842,"style":87},"329.9","194.0",[86,86],[72,844,849],{"x":845,"y":846,"fill":69,"style":847,"textAnchor":848},"197.1","92.0","font-size:13px","end","Render all rows",[49,851],{"x":852,"y":96,"width":853,"height":854,"rx":99,"fill":118,"stroke":83,"style":855},"209.1","458.9","19","fill-opacity:0.7;stroke-opacity:0.9",[72,857,860],{"x":858,"y":846,"fill":69,"style":859},"674.0","font-size:12px;font-weight:600","190ms",[72,862,864],{"x":845,"y":863,"fill":69,"style":847,"textAnchor":848},"123.0","First 40 rows, rest after paint",[49,866],{"x":852,"y":867,"width":868,"height":854,"rx":99,"fill":118,"stroke":83,"style":855},"109.0","140.1",[72,870,872],{"x":871,"y":863,"fill":69,"style":859},"355.2","58ms",[72,874,876],{"x":845,"y":875,"fill":69,"style":847,"textAnchor":848},"154.0","+ content-visibility",[49,878],{"x":852,"y":879,"width":880,"height":854,"rx":99,"fill":105,"stroke":105,"style":881},"140.0","99.0","fill-opacity:0.55;stroke-opacity:0.9",[72,883,885],{"x":884,"y":875,"fill":69,"style":859},"314.1","41ms",[72,887,889],{"x":845,"y":888,"fill":69,"style":847,"textAnchor":848},"185.0","+ contained panel",[49,891],{"x":852,"y":892,"width":893,"height":854,"rx":99,"fill":105,"stroke":105,"style":881},"171.0","79.7",[72,895,897],{"x":896,"y":888,"fill":69,"style":859},"294.8","33ms",[72,899,900],{"x":840,"y":171,"fill":172,"style":173,"textAnchor":112},"50ms target",[181,902,904],{"id":903},"verification","Verification",[15,906,907,908,910,911,915],{},"In a 4x-throttled trace, the interaction's rendering tasks should total under ",[30,909,198],{},", and Recalculate Style should affect hundreds rather than thousands of elements. Paint flashing should be limited to the changed panel. In RUM, the presentation-delay component of INP for the affected interaction target should fall; if your beacons include LoAF data, the render tail measured as described in ",[19,912,914],{"href":913},"\u002Fcore-web-vitals-measurement\u002Flong-animation-frames-api\u002Fdiagnosing-presentation-delay-with-loaf\u002F","diagnosing presentation delay with LoAF"," is the most direct confirmation.",[404,917,919],{"className":406,"code":918,"language":408,"meta":409,"style":409},"\u002F\u002F Quick lab check: time from click to the next frame after the update.\nbutton.addEventListener('click', () => {\n  const t0 = performance.now();\n  requestAnimationFrame(() => setTimeout(() => console.log('to next frame', performance.now() - t0), 0));\n});\n\u002F\u002F trade-off: this approximates the interaction latency on your machine; it\n\u002F\u002F does not include input delay or match the Event Timing duration exactly.\n",[30,920,921,926,947,965,1009,1014,1019],{"__ignoreMap":409},[413,922,923],{"class":78,"line":415},[413,924,925],{"class":481},"\u002F\u002F Quick lab check: time from click to the next frame after the update.\n",[413,927,928,931,934,936,940,943,945],{"class":78,"line":440},[413,929,930],{"class":429},"button.",[413,932,933],{"class":425},"addEventListener",[413,935,430],{"class":429},[413,937,939],{"class":938},"sZ8jY","'click'",[413,941,942],{"class":429},", () ",[413,944,506],{"class":418},[413,946,688],{"class":429},[413,948,949,951,954,956,959,962],{"class":78,"line":472},[413,950,443],{"class":418},[413,952,953],{"class":446}," t0",[413,955,450],{"class":418},[413,957,958],{"class":429}," performance.",[413,960,961],{"class":425},"now",[413,963,964],{"class":429},"();\n",[413,966,967,970,972,974,976,978,980,983,986,988,991,994,996,999,1002,1005,1007],{"class":78,"line":485},[413,968,969],{"class":425},"  requestAnimationFrame",[413,971,512],{"class":429},[413,973,506],{"class":418},[413,975,517],{"class":425},[413,977,512],{"class":429},[413,979,506],{"class":418},[413,981,982],{"class":429}," console.",[413,984,985],{"class":425},"log",[413,987,430],{"class":429},[413,989,990],{"class":938},"'to next frame'",[413,992,993],{"class":429},", performance.",[413,995,961],{"class":425},[413,997,998],{"class":429},"() ",[413,1000,1001],{"class":418},"-",[413,1003,1004],{"class":429}," t0), ",[413,1006,53],{"class":446},[413,1008,592],{"class":429},[413,1010,1011],{"class":78,"line":531},[413,1012,1013],{"class":429},"});\n",[413,1015,1016],{"class":78,"line":573},[413,1017,1018],{"class":481},"\u002F\u002F trade-off: this approximates the interaction latency on your machine; it\n",[413,1020,1021],{"class":78,"line":595},[413,1022,1023],{"class":481},"\u002F\u002F does not include input delay or match the Event Timing duration exactly.\n",[181,1025,1027],{"id":1026},"framework-notes","Framework Notes",[15,1029,1030,1033,1034,1037],{},[192,1031,1032],{},"React."," Wrapping the state change that reveals the large content in ",[30,1035,1036],{},"startTransition"," lets React render it interruptibly, but the commit is still one DOM mutation — presentation delay is unchanged unless the content itself is smaller. Combine transitions with virtualisation or chunked rendering.",[15,1039,1040,1043,1044,1047,1048,1051,1052,1055],{},[192,1041,1042],{},"Vue."," A ",[30,1045,1046],{},"v-for"," over a large array re-renders the whole list on change. Use ",[30,1049,1050],{},"v-memo"," on rows whose inputs have not changed, and ",[30,1053,1054],{},"shallowRef"," for large arrays to avoid deep reactivity overhead before rendering even starts.",[15,1057,1058,1061,1062,1065,1066,1069,1070,1073,1074,1077],{},[192,1059,1060],{},"Angular."," Use ",[30,1063,1064],{},"trackBy"," (or the ",[30,1067,1068],{},"track"," expression in ",[30,1071,1072],{},"@for",") so changing a list does not destroy and recreate every row, and ",[30,1075,1076],{},"@defer (on viewport)"," for heavy below-the-fold sections.",[181,1079,1081],{"id":1080},"designing-interactions-that-render-less","Designing Interactions That Render Less",[15,1083,1084],{},"The cheapest DOM update is the one the design does not require. When a tab switch or filter consistently produces large presentation delay, revisit the interaction itself with the product team. Can the default tab show a summary rather than the full table? Can \"show all\" become pagination of 50 at a time? Can a view-mode toggle switch a class on a container that already has both layouts' styles loaded, instead of re-rendering items with different markup? Each of these reduces the work per interaction structurally, rather than optimising work that need not exist. The techniques in this guide then handle the cases where large updates are genuinely necessary.",[181,1086,1088],{"id":1087},"faq","FAQ",[1090,1091,1094,1098],"details",{"className":1092},[1093],"faq-item",[1095,1096,1097],"summary",{},"Does using a DocumentFragment make insertion cheaper?",[15,1099,1100],{},"Building nodes in a fragment avoids repeated layout during construction if your code would otherwise read layout between insertions. But the rendering cost of the final content — style, layout, paint for all its nodes — is the same once it is inserted. Fragments fix thrashing, not volume.",[1090,1102,1104,1107],{"className":1103},[1093],[1095,1105,1106],{},"Is presentation delay affected by the GPU?",[15,1108,1109,1110,1113],{},"Yes. Paint and raster run partly on the GPU and its process; on low-end devices with high-resolution screens, rasterising a large changed area can dominate presentation delay. Reducing the painted area (containment, smaller updates) helps; heavy effects like ",[30,1111,1112],{},"backdrop-filter"," and large blurred shadows are disproportionately expensive there.",[1090,1115,1117,1120],{"className":1116},[1093],[1095,1118,1119],{},"Should large updates use a View Transition to hide the cost?",[15,1121,1122],{},"A view transition can make a large swap look smoother, but it does not reduce rendering cost — the browser still has to render the new state, and capturing snapshots adds work before the update. For INP, the next paint after the interaction is still delayed by the full render. Use transitions for polish once the update itself is cheap, not as a way to mask an expensive one.",[181,1124,1126],{"id":1125},"related","Related",[186,1128,1129,1135,1142],{},[189,1130,1131,1134],{},[19,1132,1133],{"href":913},"Diagnosing presentation delay with LoAF"," — measuring the render tail in the field.",[189,1136,1137,1141],{},[19,1138,1140],{"href":1139},"\u002Frendering-css-performance\u002Fdom-size-and-list-virtualization\u002Ffixing-the-excessive-dom-size-warning\u002F","Fixing the excessive DOM size warning"," — reducing how much DOM exists at all.",[189,1143,1144,1148],{},[19,1145,1147],{"href":1146},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Ffixing-inp-from-forced-layout-in-event-handlers\u002F","Fixing INP from forced layout in event handlers"," — when layout runs inside your script instead.",[1150,1151,1153],"script",{"type":1152},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Reduce Presentation Delay from Large DOM Updates\",\n  \"description\": \"Techniques for making the DOM change behind an interaction smaller and cheaper to render, so presentation delay stays under 50ms.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Render the first screenful now, the rest after paint\",\n      \"text\": \"Split the update: commit what is visible immediately, then append the remainder in the next task, after the browser has painted the response to the interaction.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Skip rendering off-screen content with content-visibility\",\n      \"text\": \"Expected outcome: style and layout cost scales with visible content rather than total content.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Contain the changed region\",\n      \"text\": \"Expected outcome: Layout's root becomes the panel instead of the document, and paint is limited to the panel area.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"Make each item cheaper to render\",\n      \"text\": \"Flatten wrapper divs, replace per-item box-shadow and filter with simpler borders or a shared background, and avoid selectors like .grid > + that match across the whole list.\"\n    }\n  ]\n}\n",[1150,1155,1156],{"type":1152},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Reduce Presentation Delay from Large DOM Updates\",\n  \"description\": \"Techniques for making the DOM change behind an interaction smaller and cheaper to render, so presentation delay stays under 50ms.\",\n  \"datePublished\": \"2026-10-06\",\n  \"dateModified\": \"2026-10-06\",\n  \"author\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"publisher\": {\n    \"@type\": \"Organization\",\n    \"name\": \"frontend-performance.com\"\n  },\n  \"mainEntityOfPage\": {\n    \"@type\": \"WebPage\",\n    \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates\u002F\"\n  }\n}\n",[1150,1158,1159],{"type":1152},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 1,\n      \"name\": \"Home\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 2,\n      \"name\": \"Core Web Vitals & Measurement\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Profiling Event Handlers for INP\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Reducing Presentation Delay from Large DOM Updates\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates\u002F\"\n    }\n  ]\n}\n",[1161,1162,1163],"style",{},"html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}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 .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}",{"title":409,"searchDepth":440,"depth":440,"links":1165},[1166,1167,1168,1174,1175,1176,1177,1178],{"id":183,"depth":440,"text":184},{"id":233,"depth":440,"text":234},{"id":393,"depth":440,"text":394,"children":1169},[1170,1171,1172,1173],{"id":398,"depth":472,"text":399},{"id":667,"depth":472,"text":668},{"id":758,"depth":472,"text":759},{"id":799,"depth":472,"text":800},{"id":903,"depth":440,"text":904},{"id":1026,"depth":440,"text":1027},{"id":1080,"depth":440,"text":1081},{"id":1087,"depth":440,"text":1088},{"id":1125,"depth":440,"text":1126},"Techniques for making the DOM change behind an interaction smaller and cheaper to render, so presentation delay stays under 50ms.","md",{"slug":1182,"type":1183,"breadcrumb":1184,"datePublished":1192,"dateModified":1192},"reducing-presentation-delay-from-large-dom-updates","article",[1185,1188,1189,1190],{"name":1186,"url":1187},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1191},"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates\u002F","2026-10-06",true,"\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates",{"title":1196,"description":1197},"Reducing INP Presentation Delay from Large DOM Updates","When a click replaces hundreds of nodes, style, layout and paint push INP past 200ms. Shrink, chunk and contain the DOM update to cut presentation delay.","core-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-presentation-delay-from-large-dom-updates\u002Findex","QgcFoFgUIi2eVHC1LPXL3gCDUWZTZRU43qlvCQM2ZFo",[1201,1205],{"title":1202,"path":1203,"stem":1204,"children":-1},"Reducing Input Delay from Third-Party Tags","\u002Fcore-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-input-delay-from-third-party-tags","core-web-vitals-measurement\u002Fprofiling-event-handlers-for-inp\u002Freducing-input-delay-from-third-party-tags\u002Findex",{"title":1206,"path":1207,"stem":1208,"children":-1},"Reducing Cumulative Layout Shift (CLS)","\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls","core-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Findex",1791308073799]