[{"data":1,"prerenderedAt":1249},["ShallowReactive",2],{"content:\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes":3,"surroundings:\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes":1241},{"id":4,"title":5,"body":6,"description":1220,"extension":1221,"meta":1222,"navigation":1234,"path":1235,"seo":1236,"stem":1239,"__hash__":1240},"content\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002Findex.md","Batching DOM Reads and Writes",{"type":7,"value":8,"toc":1203},"minimark",[9,14,29,32,134,139,168,172,178,184,190,196,200,205,364,368,620,631,635,642,646,657,719,723,726,730,737,861,865,875,1020,1024,1050,1054,1060,1066,1072,1078,1082,1100,1109,1118,1127,1139,1148,1161,1165,1188,1193,1196,1199],[10,11,13],"h1",{"id":12},"how-to-batch-dom-reads-and-writes","How to Batch DOM Reads and Writes",[15,16,17,18,23,24,28],"p",{},"This guide is part of ",[19,20,22],"a",{"href":21},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F","Layout Thrashing & Forced Reflow",", within ",[19,25,27],{"href":26},"\u002Frendering-css-performance\u002F","Rendering & CSS Performance",". Layout thrashing happens when code alternates between changing the DOM (writes) and measuring it (reads). Each read after a write forces the browser to compute layout synchronously. The fix is conceptually simple: do all the reads first, then all the writes, so layout runs at most once. In practice, reads and writes are spread across functions, components and libraries, and keeping them separated needs a pattern that survives as code grows.",[15,30,31],{},"This guide covers three levels: restructuring a single function into phases, coordinating reads and writes across modules with a scheduler, and avoiding reads entirely by caching geometry or using observers.",[15,33,34],{},[35,36,42,43,42,50,42,54,42,57,42,66,42,72,42,81,42,87,42,92,42,97,42,100,42,103,42,106,42,109,42,114,42,118,42,120,42,124,42,126,42,129,42,131,42],"svg",{"viewBox":37,"width":38,"role":39,"ariaLabel":40,"style":41},"0 0 760 204","100%","img","Comparison of code that interleaves DOM reads and writes with code that batches them.","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)"," ",[44,45],"rect",{"className":46,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],"svg-canvas","0","#ffffff",[51,52,53],"title",{},"Interleaved vs batched DOM access",[55,56,40],"desc",{},[44,58],{"x":59,"y":59,"width":60,"height":61,"rx":62,"fill":63,"stroke":64,"style":65},"1","758","202","10","none","currentColor","stroke-opacity:0.18",[67,68,53],"text",{"x":69,"y":70,"fill":64,"style":71},"28.0","34.0","font-size:16px;font-weight:700",[44,73],{"x":69,"y":74,"width":75,"height":76,"rx":77,"fill":78,"stroke":79,"style":80},"56.0","340.0","126.0","6","#ffc300","#b8860b","fill-opacity:0.24;stroke-opacity:0.9",[67,82,86],{"x":83,"y":84,"fill":64,"style":85},"42.0","82.0","font-size:14px;font-weight:700","Interleaved",[67,88,91],{"x":83,"y":89,"fill":64,"style":90},"108.0","font-size:12.5px;font-weight:700","•",[67,93,96],{"x":74,"y":89,"fill":64,"style":94,"textAnchor":95},"font-size:12.5px","start","write, read, write, read...",[67,98,91],{"x":83,"y":99,"fill":64,"style":90},"132.0",[67,101,102],{"x":74,"y":99,"fill":64,"style":94,"textAnchor":95},"Layout forced on every read",[67,104,91],{"x":83,"y":105,"fill":64,"style":90},"156.0",[67,107,108],{"x":74,"y":105,"fill":64,"style":94,"textAnchor":95},"Cost grows with item count",[44,110],{"x":111,"y":74,"width":75,"height":76,"rx":77,"fill":112,"stroke":112,"style":113},"392.0","#0466c8","fill-opacity:0.14;stroke-opacity:0.9",[67,115,117],{"x":116,"y":84,"fill":64,"style":85},"406.0","Batched",[67,119,91],{"x":116,"y":89,"fill":64,"style":90},[67,121,123],{"x":122,"y":89,"fill":64,"style":94,"textAnchor":95},"420.0","all reads, then all writes",[67,125,91],{"x":116,"y":99,"fill":64,"style":90},[67,127,128],{"x":122,"y":99,"fill":64,"style":94,"textAnchor":95},"At most one forced layout",[67,130,91],{"x":116,"y":105,"fill":64,"style":90},[67,132,133],{"x":122,"y":105,"fill":64,"style":94,"textAnchor":95},"Cost roughly constant",[135,136,138],"h2",{"id":137},"rapid-diagnosis","Rapid Diagnosis",[140,141,142,150,156,162],"ul",{},[143,144,145,149],"li",{},[146,147,148],"strong",{},"Find forced reflow warnings"," in a Performance trace and note the functions involved.",[143,151,152,155],{},[146,153,154],{},"Look for loops"," that both set styles and read geometry.",[143,157,158,161],{},[146,159,160],{},"Look for components"," that measure themselves in mount or update hooks, many at once.",[143,163,164,167],{},[146,165,166],{},"Check scroll and resize handlers"," for both reading and writing.",[135,169,171],{"id":170},"root-cause-analysis","Root Cause Analysis",[15,173,174,177],{},[146,175,176],{},"1. Natural code order."," It is intuitive to measure an element and immediately adjust it.",[15,179,180,183],{},[146,181,182],{},"2. Distributed responsibilities."," Each component measures and adjusts itself, unaware of others doing the same.",[15,185,186,189],{},[146,187,188],{},"3. Helper functions with hidden reads."," Utilities measure inside otherwise write-only code.",[15,191,192,195],{},[146,193,194],{},"4. Event handlers that do everything."," One handler updates state, applies styles and measures the result.",[135,197,199],{"id":198},"step-by-step-resolution","Step-by-Step Resolution",[201,202,204],"h3",{"id":203},"_1-split-one-function-into-read-and-write-phases","1. Split one function into read and write phases",[206,207,212],"pre",{"className":208,"code":209,"language":210,"meta":211,"style":211},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","function syncLabelWidths(labels) {\n  \u002F\u002F Read phase\n  const widths = labels.map((l) => l.scrollWidth);\n  const max = Math.max(...widths);\n  \u002F\u002F Write phase\n  for (const l of labels) l.style.width = `${max}px`;\n}\n\u002F\u002F trade-off: the read phase sees the layout from before any writes in this\n\u002F\u002F function, which is exactly what you want for equalising sizes.\n","javascript","",[213,214,215,239,246,280,304,310,346,352,358],"code",{"__ignoreMap":211},[216,217,220,224,228,232,236],"span",{"class":218,"line":219},"line",1,[216,221,223],{"class":222},"sPARh","function",[216,225,227],{"class":226},"smZ65"," syncLabelWidths",[216,229,231],{"class":230},"saISM","(",[216,233,235],{"class":234},"sQw3B","labels",[216,237,238],{"class":230},") {\n",[216,240,242],{"class":218,"line":241},2,[216,243,245],{"class":244},"sjfSM","  \u002F\u002F Read phase\n",[216,247,249,252,256,259,262,265,268,271,274,277],{"class":218,"line":248},3,[216,250,251],{"class":222},"  const",[216,253,255],{"class":254},"sPXB4"," widths",[216,257,258],{"class":222}," =",[216,260,261],{"class":230}," labels.",[216,263,264],{"class":226},"map",[216,266,267],{"class":230},"((",[216,269,270],{"class":234},"l",[216,272,273],{"class":230},") ",[216,275,276],{"class":222},"=>",[216,278,279],{"class":230}," l.scrollWidth);\n",[216,281,283,285,288,290,293,296,298,301],{"class":218,"line":282},4,[216,284,251],{"class":222},[216,286,287],{"class":254}," max",[216,289,258],{"class":222},[216,291,292],{"class":230}," Math.",[216,294,295],{"class":226},"max",[216,297,231],{"class":230},[216,299,300],{"class":222},"...",[216,302,303],{"class":230},"widths);\n",[216,305,307],{"class":218,"line":306},5,[216,308,309],{"class":244},"  \u002F\u002F Write phase\n",[216,311,313,316,319,322,325,328,331,334,338,340,343],{"class":218,"line":312},6,[216,314,315],{"class":222},"  for",[216,317,318],{"class":230}," (",[216,320,321],{"class":222},"const",[216,323,324],{"class":254}," l",[216,326,327],{"class":222}," of",[216,329,330],{"class":230}," labels) l.style.width ",[216,332,333],{"class":222},"=",[216,335,337],{"class":336},"sZ8jY"," `${",[216,339,295],{"class":230},[216,341,342],{"class":336},"}px`",[216,344,345],{"class":230},";\n",[216,347,349],{"class":218,"line":348},7,[216,350,351],{"class":230},"}\n",[216,353,355],{"class":218,"line":354},8,[216,356,357],{"class":244},"\u002F\u002F trade-off: the read phase sees the layout from before any writes in this\n",[216,359,361],{"class":218,"line":360},9,[216,362,363],{"class":244},"\u002F\u002F function, which is exactly what you want for equalising sizes.\n",[201,365,367],{"id":366},"_2-coordinate-across-modules-with-a-frame-scheduler","2. Coordinate across modules with a frame scheduler",[206,369,371],{"className":208,"code":370,"language":210,"meta":211,"style":211},"\u002F\u002F Minimal read\u002Fwrite scheduler (the idea behind fastdom).\nconst reads = [], writes = [];\nlet scheduled = false;\nfunction flush() {\n  scheduled = false;\n  reads.splice(0).forEach((fn) => fn());   \u002F\u002F all reads first\n  writes.splice(0).forEach((fn) => fn());  \u002F\u002F then all writes\n}\nfunction schedule() { if (!scheduled) { scheduled = true; requestAnimationFrame(flush); } }\nexport const measure = (fn) => { reads.push(fn); schedule(); };\nexport const mutate = (fn) => { writes.push(fn); schedule(); };\n\u002F\u002F trade-off: work is deferred to the next frame, adding up to ~16ms latency, and\n\u002F\u002F a write that triggers a new read must queue it for the following frame.\n",[213,372,373,378,398,413,423,434,470,501,505,540,577,608,614],{"__ignoreMap":211},[216,374,375],{"class":218,"line":219},[216,376,377],{"class":244},"\u002F\u002F Minimal read\u002Fwrite scheduler (the idea behind fastdom).\n",[216,379,380,382,385,387,390,393,395],{"class":218,"line":241},[216,381,321],{"class":222},[216,383,384],{"class":254}," reads",[216,386,258],{"class":222},[216,388,389],{"class":230}," [], ",[216,391,392],{"class":254},"writes",[216,394,258],{"class":222},[216,396,397],{"class":230}," [];\n",[216,399,400,403,406,408,411],{"class":218,"line":248},[216,401,402],{"class":222},"let",[216,404,405],{"class":230}," scheduled ",[216,407,333],{"class":222},[216,409,410],{"class":254}," false",[216,412,345],{"class":230},[216,414,415,417,420],{"class":218,"line":282},[216,416,223],{"class":222},[216,418,419],{"class":226}," flush",[216,421,422],{"class":230},"() {\n",[216,424,425,428,430,432],{"class":218,"line":306},[216,426,427],{"class":230},"  scheduled ",[216,429,333],{"class":222},[216,431,410],{"class":254},[216,433,345],{"class":230},[216,435,436,439,442,444,446,449,452,454,457,459,461,464,467],{"class":218,"line":312},[216,437,438],{"class":230},"  reads.",[216,440,441],{"class":226},"splice",[216,443,231],{"class":230},[216,445,48],{"class":254},[216,447,448],{"class":230},").",[216,450,451],{"class":226},"forEach",[216,453,267],{"class":230},[216,455,456],{"class":234},"fn",[216,458,273],{"class":230},[216,460,276],{"class":222},[216,462,463],{"class":226}," fn",[216,465,466],{"class":230},"());   ",[216,468,469],{"class":244},"\u002F\u002F all reads first\n",[216,471,472,475,477,479,481,483,485,487,489,491,493,495,498],{"class":218,"line":348},[216,473,474],{"class":230},"  writes.",[216,476,441],{"class":226},[216,478,231],{"class":230},[216,480,48],{"class":254},[216,482,448],{"class":230},[216,484,451],{"class":226},[216,486,267],{"class":230},[216,488,456],{"class":234},[216,490,273],{"class":230},[216,492,276],{"class":222},[216,494,463],{"class":226},[216,496,497],{"class":230},"());  ",[216,499,500],{"class":244},"\u002F\u002F then all writes\n",[216,502,503],{"class":218,"line":354},[216,504,351],{"class":230},[216,506,507,509,512,515,518,520,523,526,528,531,534,537],{"class":218,"line":360},[216,508,223],{"class":222},[216,510,511],{"class":226}," schedule",[216,513,514],{"class":230},"() { ",[216,516,517],{"class":222},"if",[216,519,318],{"class":230},[216,521,522],{"class":222},"!",[216,524,525],{"class":230},"scheduled) { scheduled ",[216,527,333],{"class":222},[216,529,530],{"class":254}," true",[216,532,533],{"class":230},"; ",[216,535,536],{"class":226},"requestAnimationFrame",[216,538,539],{"class":230},"(flush); } }\n",[216,541,543,546,549,552,554,556,558,560,562,565,568,571,574],{"class":218,"line":542},10,[216,544,545],{"class":222},"export",[216,547,548],{"class":222}," const",[216,550,551],{"class":226}," measure",[216,553,258],{"class":222},[216,555,318],{"class":230},[216,557,456],{"class":234},[216,559,273],{"class":230},[216,561,276],{"class":222},[216,563,564],{"class":230}," { reads.",[216,566,567],{"class":226},"push",[216,569,570],{"class":230},"(fn); ",[216,572,573],{"class":226},"schedule",[216,575,576],{"class":230},"(); };\n",[216,578,580,582,584,587,589,591,593,595,597,600,602,604,606],{"class":218,"line":579},11,[216,581,545],{"class":222},[216,583,548],{"class":222},[216,585,586],{"class":226}," mutate",[216,588,258],{"class":222},[216,590,318],{"class":230},[216,592,456],{"class":234},[216,594,273],{"class":230},[216,596,276],{"class":222},[216,598,599],{"class":230}," { writes.",[216,601,567],{"class":226},[216,603,570],{"class":230},[216,605,573],{"class":226},[216,607,576],{"class":230},[216,609,611],{"class":218,"line":610},12,[216,612,613],{"class":244},"\u002F\u002F trade-off: work is deferred to the next frame, adding up to ~16ms latency, and\n",[216,615,617],{"class":218,"line":616},13,[216,618,619],{"class":244},"\u002F\u002F a write that triggers a new read must queue it for the following frame.\n",[15,621,622,623,626,627,630],{},"Components call ",[213,624,625],{},"measure()"," for reads and ",[213,628,629],{},"mutate()"," for writes; the scheduler guarantees reads run together before writes.",[201,632,634],{"id":633},"_3-cache-geometry-that-rarely-changes","3. Cache geometry that rarely changes",[15,636,637,638,641],{},"If code repeatedly reads values that only change on resize (container widths, header height), read them once and update the cache from a ",[213,639,640],{},"ResizeObserver"," callback instead of reading on every use.",[201,643,645],{"id":644},"_4-in-frameworks-measure-in-one-place","4. In frameworks, measure in one place",[15,647,648,649,652,653,656],{},"In React, read layout in a single ",[213,650,651],{},"useLayoutEffect"," at a parent that measures all children, rather than in each child. In Vue, batch measurements in ",[213,654,655],{},"nextTick"," callbacks. Avoid setting state from measurements in ways that trigger another render and another measurement per item.",[15,658,659],{},[35,660,42,663,42,666,42,669,42,671,42,674,42,676,42,683,42,690,42,695,42,699,42,704,42,708,42,711,42,715,42],{"viewBox":661,"width":38,"role":39,"ariaLabel":662,"style":41},"0 0 760 163","Bar chart comparing task duration for interleaved, phase-split and scheduler-based approaches to aligning label widths.",[44,664],{"className":665,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,667,668],{},"Task duration for aligning 120 label widths",[55,670,662],{},[44,672],{"x":59,"y":59,"width":60,"height":673,"rx":62,"fill":63,"stroke":64,"style":65},"161",[67,675,668],{"x":69,"y":70,"fill":64,"style":71},[67,677,682],{"x":678,"y":679,"fill":64,"style":680,"textAnchor":681},"220.4","70.0","font-size:13px","end","Interleaved per label",[44,684],{"x":685,"y":74,"width":686,"height":687,"rx":688,"fill":63,"stroke":64,"style":689},"232.4","435.6","19","3","fill-opacity:0.7;stroke-opacity:0.35",[67,691,694],{"x":692,"y":679,"fill":64,"style":693},"674.0","font-size:12px;font-weight:600","96ms",[67,696,698],{"x":678,"y":697,"fill":64,"style":680,"textAnchor":681},"101.0","Read phase then write phase",[44,700],{"x":685,"y":701,"width":702,"height":687,"rx":688,"fill":78,"stroke":79,"style":703},"87.0","31.8","fill-opacity:0.7;stroke-opacity:0.9",[67,705,707],{"x":706,"y":697,"fill":64,"style":693},"270.1","7ms",[67,709,710],{"x":678,"y":99,"fill":64,"style":680,"textAnchor":681},"Frame scheduler across modules",[44,712],{"x":685,"y":713,"width":714,"height":687,"rx":688,"fill":78,"stroke":79,"style":703},"118.0","36.3",[67,716,718],{"x":717,"y":99,"fill":64,"style":693},"274.7","8ms",[135,720,722],{"id":721},"verification","Verification",[15,724,725],{},"Record the interaction again: Layout events inside script should be gone or reduced to one per task, and the forced reflow warnings should disappear. Check the task duration and the interaction's duration in the Interactions track. Confirm visual behaviour is unchanged — deferring writes to the next frame can occasionally show one frame of the old state, which may matter for drag interactions.",[135,727,729],{"id":728},"worked-example-a-kanban-board","Worked Example: A Kanban Board",[15,731,732,733,736],{},"A project management app's Kanban board auto-sized card titles: each card component measured its title in a mount effect and adjusted the font size if it overflowed. Loading a board with 300 cards triggered 300 forced reflows in one task (520ms on a mid-range laptop), and moving a card re-ran the logic for every card in two columns. The team replaced per-card logic with a board-level effect that read all title overflow states in one pass, then applied font sizes in a second pass, and later replaced font fitting with CSS ",[213,734,735],{},"text-overflow"," and a two-line clamp. Board load dropped to 70ms of rendering, and card drag interactions stopped dropping frames.",[15,738,739],{},[35,740,42,743,42,746,42,749,42,751,42,768,42,771,42,773,42,777,42,784,42,788,42,793,42,796,42,799,42,803,42,807,42,810,42,814,42,817,42,821,42,824,42,828,42,831,42,834,42,838,42,841,42,849,42,853,42,857,42],{"viewBox":741,"width":38,"role":39,"ariaLabel":742,"style":41},"0 0 760 166","Order of work inside one animation frame when reads and writes are scheduled separately.",[44,744],{"className":745,"x":48,"y":48,"width":38,"height":38,"fill":49},[47],[51,747,748],{},"One frame with a read\u002Fwrite scheduler",[55,750,742],{},[752,753,754],"defs",{},[755,756,763],"marker",{"id":757,"viewBox":758,"refX":759,"refY":760,"markerWidth":761,"markerHeight":761,"orient":762},"fa075e213f","0 0 10 10","9","5","7","auto-start-reverse",[764,765],"path",{"d":766,"fill":64,"style":767},"M0 0 L10 5 L0 10 z","fill-opacity:0.7",[44,769],{"x":59,"y":59,"width":60,"height":770,"rx":62,"fill":63,"stroke":64,"style":65},"164",[67,772,748],{"x":69,"y":70,"fill":64,"style":71},[44,774],{"x":69,"y":74,"width":775,"height":776,"rx":77,"fill":112,"stroke":112,"style":113},"108.8","88.0",[67,778,783],{"x":779,"y":780,"fill":64,"style":781,"textAnchor":782},"82.4","78.0","font-size:13px;font-weight:700","middle","Events queue",[67,785,787],{"x":779,"y":786,"fill":64,"style":781,"textAnchor":782},"95.0","work",[67,789,792],{"x":779,"y":790,"fill":64,"style":791,"textAnchor":782},"113.0","font-size:12px","measure() +",[67,794,629],{"x":779,"y":795,"fill":64,"style":791,"textAnchor":782},"129.0",[44,797],{"x":798,"y":74,"width":775,"height":776,"rx":77,"fill":112,"stroke":112,"style":113},"176.8",[67,800,802],{"x":801,"y":780,"fill":64,"style":781,"textAnchor":782},"231.2","rAF flush",[67,804,806],{"x":801,"y":805,"fill":64,"style":791,"textAnchor":782},"96.0","start of frame",[44,808],{"x":809,"y":74,"width":775,"height":776,"rx":77,"fill":78,"stroke":79,"style":80},"325.6",[67,811,813],{"x":812,"y":780,"fill":64,"style":781,"textAnchor":782},"380.0","All reads",[67,815,816],{"x":812,"y":805,"fill":64,"style":791,"textAnchor":782},"layout already",[67,818,820],{"x":812,"y":819,"fill":64,"style":791,"textAnchor":782},"112.0","clean",[44,822],{"x":823,"y":74,"width":775,"height":776,"rx":77,"fill":78,"stroke":79,"style":80},"474.4",[67,825,827],{"x":826,"y":780,"fill":64,"style":781,"textAnchor":782},"528.8","All writes",[67,829,830],{"x":826,"y":805,"fill":64,"style":791,"textAnchor":782},"layout dirty once",[44,832],{"x":833,"y":74,"width":775,"height":776,"rx":77,"fill":112,"stroke":112,"style":113},"623.2",[67,835,837],{"x":836,"y":780,"fill":64,"style":781,"textAnchor":782},"677.6","Browser layout",[67,839,840],{"x":836,"y":805,"fill":64,"style":791,"textAnchor":782},"once, then paint",[218,842],{"x1":843,"y1":844,"x2":845,"y2":844,"stroke":64,"strokeWidth":846,"style":847,"markerEnd":848},"136.8","100.0","174.8","1.5","stroke-opacity:0.6","url(#fa075e213f)",[218,850],{"x1":851,"y1":844,"x2":852,"y2":844,"stroke":64,"strokeWidth":846,"style":847,"markerEnd":848},"285.6","323.6",[218,854],{"x1":855,"y1":844,"x2":856,"y2":844,"stroke":64,"strokeWidth":846,"style":847,"markerEnd":848},"434.4","472.4",[218,858],{"x1":859,"y1":844,"x2":860,"y2":844,"stroke":64,"strokeWidth":846,"style":847,"markerEnd":848},"583.2","621.2",[135,862,864],{"id":863},"batching-in-animation-loops","Batching in Animation Loops",[15,866,867,868,870,871,874],{},"Animation loops in ",[213,869,536],{}," are a common place for thrashing: each frame reads positions, computes new ones and writes them for many elements. Structure each frame strictly: read everything needed at the start of the callback (when layout is clean from the previous frame), compute, then write everything. Better still, animate ",[213,872,873],{},"transform"," values computed from your own state rather than reading positions back from the DOM each frame; your state is already the source of truth, so no read is needed at all. For physics or drag interactions, keep positions in JavaScript variables and only write transforms.",[206,876,878],{"className":208,"code":877,"language":210,"meta":211,"style":211},"let x = 0, v = 0;\nfunction frame(t) {\n  v += (target - x) * 0.1; v *= 0.8; x += v;         \u002F\u002F compute from state, no reads\n  el.style.transform = `translateX(${x}px)`;          \u002F\u002F single write\n  if (Math.abs(v) > 0.1) requestAnimationFrame(frame);\n}\n\u002F\u002F trade-off: if layout changes elsewhere (resize), the cached target may be stale;\n\u002F\u002F update it from a ResizeObserver rather than reading inside the loop.\n",[213,879,880,901,915,958,980,1006,1010,1015],{"__ignoreMap":211},[216,881,882,884,887,889,892,895,897,899],{"class":218,"line":219},[216,883,402],{"class":222},[216,885,886],{"class":230}," x ",[216,888,333],{"class":222},[216,890,891],{"class":254}," 0",[216,893,894],{"class":230},", v ",[216,896,333],{"class":222},[216,898,891],{"class":254},[216,900,345],{"class":230},[216,902,903,905,908,910,913],{"class":218,"line":241},[216,904,223],{"class":222},[216,906,907],{"class":226}," frame",[216,909,231],{"class":230},[216,911,912],{"class":234},"t",[216,914,238],{"class":230},[216,916,917,920,923,926,929,932,935,938,941,944,947,950,952,955],{"class":218,"line":248},[216,918,919],{"class":230},"  v ",[216,921,922],{"class":222},"+=",[216,924,925],{"class":230}," (target ",[216,927,928],{"class":222},"-",[216,930,931],{"class":230}," x) ",[216,933,934],{"class":222},"*",[216,936,937],{"class":254}," 0.1",[216,939,940],{"class":230},"; v ",[216,942,943],{"class":222},"*=",[216,945,946],{"class":254}," 0.8",[216,948,949],{"class":230},"; x ",[216,951,922],{"class":222},[216,953,954],{"class":230}," v;         ",[216,956,957],{"class":244},"\u002F\u002F compute from state, no reads\n",[216,959,960,963,965,968,971,974,977],{"class":218,"line":282},[216,961,962],{"class":230},"  el.style.transform ",[216,964,333],{"class":222},[216,966,967],{"class":336}," `translateX(${",[216,969,970],{"class":230},"x",[216,972,973],{"class":336},"}px)`",[216,975,976],{"class":230},";          ",[216,978,979],{"class":244},"\u002F\u002F single write\n",[216,981,982,985,988,991,994,997,999,1001,1003],{"class":218,"line":306},[216,983,984],{"class":222},"  if",[216,986,987],{"class":230}," (Math.",[216,989,990],{"class":226},"abs",[216,992,993],{"class":230},"(v) ",[216,995,996],{"class":222},">",[216,998,937],{"class":254},[216,1000,273],{"class":230},[216,1002,536],{"class":226},[216,1004,1005],{"class":230},"(frame);\n",[216,1007,1008],{"class":218,"line":312},[216,1009,351],{"class":230},[216,1011,1012],{"class":218,"line":348},[216,1013,1014],{"class":244},"\u002F\u002F trade-off: if layout changes elsewhere (resize), the cached target may be stale;\n",[216,1016,1017],{"class":218,"line":354},[216,1018,1019],{"class":244},"\u002F\u002F update it from a ResizeObserver rather than reading inside the loop.\n",[135,1021,1023],{"id":1022},"common-mistakes","Common Mistakes",[140,1025,1026,1032,1038,1044],{},[143,1027,1028,1031],{},[146,1029,1030],{},"Batching writes but leaving a read in the write loop."," One stray read reintroduces thrashing.",[143,1033,1034,1037],{},[146,1035,1036],{},"Nested schedulers."," Reads scheduled from within writes run a frame later; design for that.",[143,1039,1040,1043],{},[146,1041,1042],{},"Measuring in every component."," Many self-measuring components thrash collectively even if each is fine alone.",[143,1045,1046,1049],{},[146,1047,1048],{},"Reading back what you just wrote."," Keep values in variables instead of re-reading the DOM.",[135,1051,1053],{"id":1052},"edge-cases","Edge Cases",[15,1055,1056,1059],{},[146,1057,1058],{},"Writes that must be measured."," Sometimes you must apply a style and measure the result (for example, the natural height of expanded content). Do all such writes, then a single read phase, then the final writes — two layouts instead of hundreds.",[15,1061,1062,1065],{},[146,1063,1064],{},"Synchronous user feedback."," Deferring writes to the next frame is fine for most UI, but for pointer-following elements, write immediately and avoid reads entirely.",[15,1067,1068,1071],{},[146,1069,1070],{},"Third-party code."," You cannot batch inside libraries; call them outside your write phase, or configure them to measure less often.",[15,1073,1074,1077],{},[146,1075,1076],{},"Server-side rendering."," No layout exists on the server; guard layout reads so they only run in the browser after mount.",[135,1079,1081],{"id":1080},"faq","FAQ",[1083,1084,1087,1091],"details",{"className":1085},[1086],"faq-item",[1088,1089,1090],"summary",{},"What is fastdom?",[15,1092,1093,1094,1096,1097,1099],{},"A small library that batches DOM reads and writes into separate phases per animation frame, using ",[213,1095,625],{}," and ",[213,1098,629],{}," calls. The pattern can be implemented in a few lines.",[1083,1101,1103,1106],{"className":1102},[1086],[1088,1104,1105],{},"Does React batch DOM reads?",[15,1107,1108],{},"React batches DOM writes during commit, but reads in layout effects or event handlers are your responsibility. Multiple components reading in layout effects after writes can still thrash.",[1083,1110,1112,1115],{"className":1111},[1086],[1088,1113,1114],{},"Is requestAnimationFrame enough to batch?",[15,1116,1117],{},"Only if all reads in the callback come before all writes. rAF schedules the timing; the order inside the callback determines whether layout is forced.",[1083,1119,1121,1124],{"className":1120},[1086],[1088,1122,1123],{},"Should I use requestIdleCallback for writes?",[15,1125,1126],{},"No for visible updates: idle callbacks may run late. Use it for non-urgent background work that does not touch layout.",[1083,1128,1130,1133],{"className":1129},[1086],[1088,1131,1132],{},"How do I batch in Vue?",[15,1134,1135,1136,1138],{},"Vue applies DOM updates asynchronously; use ",[213,1137,655],{}," to read after updates, and group reads in one callback rather than many component-level ones.",[1083,1140,1142,1145],{"className":1141},[1086],[1088,1143,1144],{},"Does batching help on small pages?",[15,1146,1147],{},"Less, because each forced layout is cheap. It matters on large DOMs and in loops, where costs multiply.",[1083,1149,1151,1154],{"className":1150},[1086],[1088,1152,1153],{},"Can CSS replace my measure-and-adjust code?",[15,1155,1156,1157,1160],{},"Often. Grid with subgrid, container queries, line clamping and ",[213,1158,1159],{},"clamp()"," for font sizes remove many measurement scripts entirely, which is better than batching them.",[135,1162,1164],{"id":1163},"related","Related",[140,1166,1167,1174,1181],{},[143,1168,1169,1173],{},[19,1170,1172],{"href":1171},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Ffinding-forced-reflows-in-the-performance-panel\u002F","Finding forced reflows in the Performance panel"," — locate the problem first.",[143,1175,1176,1180],{},[19,1177,1179],{"href":1178},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fresizeobserver-vs-resize-and-scroll-handlers\u002F","ResizeObserver vs resize and scroll handlers"," — read sizes without forcing layout.",[143,1182,1183,1187],{},[19,1184,1186],{"href":1185},"\u002Frendering-css-performance\u002Fanimation-and-compositing-performance\u002Fanimating-only-transform-and-opacity\u002F","Animating only transform and opacity"," — animation without layout reads.",[1189,1190,1192],"script",{"type":1191},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"How to Batch DOM Reads and Writes\",\n  \"description\": \"Concrete patterns for separating DOM reads and writes so layout runs once per frame, in plain JavaScript and in component frameworks.\",\n  \"step\": [\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 1,\n      \"name\": \"Split one function into read and write phases\",\n      \"text\": \"Split one function into read and write phases\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 2,\n      \"name\": \"Coordinate across modules with a frame scheduler\",\n      \"text\": \"Components call measure() for reads and mutate() for writes; the scheduler guarantees reads run together before writes.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 3,\n      \"name\": \"Cache geometry that rarely changes\",\n      \"text\": \"If code repeatedly reads values that only change on resize (container widths, header height), read them once and update the cache from a ResizeObserver callback instead of reading on every use.\"\n    },\n    {\n      \"@type\": \"HowToStep\",\n      \"position\": 4,\n      \"name\": \"In frameworks, measure in one place\",\n      \"text\": \"In React, read layout in a single useLayoutEffect at a parent that measures all children, rather than in each child.\"\n    }\n  ]\n}\n",[1189,1194,1195],{"type":1191},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Batch DOM Reads and Writes\",\n  \"description\": \"Concrete patterns for separating DOM reads and writes so layout runs once per frame, in plain JavaScript and in component frameworks.\",\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\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002F\"\n  }\n}\n",[1189,1197,1198],{"type":1191},"\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\": \"Rendering & CSS Performance\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 3,\n      \"name\": \"Layout Thrashing & Forced Reflow\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002F\"\n    },\n    {\n      \"@type\": \"ListItem\",\n      \"position\": 4,\n      \"name\": \"Batching DOM Reads and Writes\",\n      \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002F\"\n    }\n  ]\n}\n",[1200,1201,1202],"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 .sjfSM, html code.shiki .sjfSM{--shiki-default:#66707B;--shiki-dark:#BDC4CC;--shiki-light:#66707B}html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}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);}",{"title":211,"searchDepth":241,"depth":241,"links":1204},[1205,1206,1207,1213,1214,1215,1216,1217,1218,1219],{"id":137,"depth":241,"text":138},{"id":170,"depth":241,"text":171},{"id":198,"depth":241,"text":199,"children":1208},[1209,1210,1211,1212],{"id":203,"depth":248,"text":204},{"id":366,"depth":248,"text":367},{"id":633,"depth":248,"text":634},{"id":644,"depth":248,"text":645},{"id":721,"depth":241,"text":722},{"id":728,"depth":241,"text":729},{"id":863,"depth":241,"text":864},{"id":1022,"depth":241,"text":1023},{"id":1052,"depth":241,"text":1053},{"id":1080,"depth":241,"text":1081},{"id":1163,"depth":241,"text":1164},"Concrete patterns for separating DOM reads and writes so layout runs once per frame, in plain JavaScript and in component frameworks.","md",{"slug":1223,"type":1224,"breadcrumb":1225,"datePublished":1233,"dateModified":1233},"batching-dom-reads-and-writes","article",[1226,1229,1230,1231],{"name":1227,"url":1228},"Home","\u002F",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1232},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002F","2026-10-06",true,"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes",{"title":1237,"description":1238},"Batching DOM Reads and Writes to Avoid Layout Thrashing","Restructure code into read and write phases, schedule writes with requestAnimationFrame or fastdom, and cache geometry so layout runs once per frame.","rendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Fbatching-dom-reads-and-writes\u002Findex","9NmW7mqKmV5qVC_-Gfn9_T4rAAbY72pVflX9V4wT__Q",[1242,1245],{"title":22,"path":1243,"stem":1244,"children":-1},"\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow","rendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Findex",{"title":1246,"path":1247,"stem":1248,"children":-1},"Finding Forced Reflows in the Performance Panel","\u002Frendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Ffinding-forced-reflows-in-the-performance-panel","rendering-css-performance\u002Flayout-thrashing-and-forced-reflow\u002Ffinding-forced-reflows-in-the-performance-panel\u002Findex",1791308072312]