[{"data":1,"prerenderedAt":1221},["ShallowReactive",2],{"content:\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio":3,"surroundings:\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio":1212},{"id":4,"title":5,"body":6,"description":1193,"extension":1194,"meta":1195,"navigation":1206,"path":1207,"seo":1208,"stem":1210,"__hash__":1211},"content\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002Findex.md","Reserving Space for Embeds with aspect-ratio",{"type":7,"value":8,"toc":1185},"minimark",[9,14,42,53,58,90,235,239,256,265,271,277,281,290,384,462,465,471,546,552,627,630,641,843,847,850,853,1036,1039,1043,1058,1082,1103,1119,1136,1145,1149,1170,1175,1178,1181],[10,11,13],"h1",{"id":12},"how-to-reserve-space-for-embeds-with-aspect-ratio","How to Reserve Space for Embeds with aspect-ratio",[15,16,17,18,23,24,28,29,33,34,37,38,41],"p",{},"This walkthrough belongs to ",[19,20,22],"a",{"href":21},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F","Reducing Cumulative Layout Shift (CLS)"," inside ",[19,25,27],{"href":26},"\u002Fcore-web-vitals-measurement\u002F","Core Web Vitals & Measurement",". An ",[30,31,32],"code",{},"\u003Ciframe>"," with no dimensions renders as a ",[30,35,36],{},"300×150"," box, and then jumps to its real size when the embedded document loads. Everything below it moves. On an article page with three embeds, that pattern alone can spend the entire ",[30,39,40],{},"0.1"," CLS budget before a single image is considered.",[15,43,44,45,48,49,52],{},"The fix is to make the box correct before the content arrives, using ",[30,46,47],{},"aspect-ratio"," where the shape is known and a measured ",[30,50,51],{},"min-height"," where it is not.",[54,55,57],"h2",{"id":56},"rapid-diagnosis-find-the-boxes-that-change-size","Rapid Diagnosis: Find the Boxes That Change Size",[59,60,61,69,78,84],"ul",{},[62,63,64,68],"li",{},[65,66,67],"strong",{},"Turn on Layout Shift Regions"," in the Rendering panel and reload. Every embed that resizes will flash a highlight over itself and everything beneath it.",[62,70,71,74,75,77],{},[65,72,73],{},"Read the shift sources."," In the Performance panel's Experience track, each shift record names the element that moved — an ",[30,76,32],{}," or its wrapper is the giveaway.",[62,79,80,83],{},[65,81,82],{},"Compare initial and final geometry."," Select the embed in the Elements panel before and after it loads; a change in computed height is your shift.",[62,85,86,89],{},[65,87,88],{},"Check whether the embed resizes itself later."," Some widgets post a height message to the parent and grow after their content renders — a second shift, well after load.",[91,92,99,100,107,108,107,112,107,116,107,125,107,132,107,139,107,147,107,152,107,158,107,162,107,168,107,177,107,182,107,189,107,194,107,197,107,200,107,205,107,210,107,214,107,220,107,224,107,228,107,230,107,232,107],"svg",{"xmlns":93,"viewBox":94,"width":95,"role":96,"ariaLabel":97,"style":98},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 250","100%","img","Two page layouts: an unreserved iframe rendering at 150 pixels then growing to 400, pushing the following paragraph down, versus a reserved box where the iframe fills a pre-sized container and nothing moves","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:var(--fp-svg-ink)","\n  ",[101,102],"rect",{"className":103,"x":105,"y":105,"width":95,"height":95,"fill":106},[104],"svg-canvas","0","#ffffff"," ",[109,110,111],"title",{},"Unreserved versus reserved embed geometry",[113,114,115],"desc",{},"Without reservation the iframe starts at the default 300 by 150 box and grows to its real height, displacing the content beneath; with aspect-ratio the container is already the final size so the embed fills it without movement.",[101,117],{"x":118,"y":118,"width":119,"height":120,"rx":121,"fill":122,"stroke":123,"style":124},"1","758","248","10","none","currentColor","stroke-opacity:0.18",[126,127,131],"text",{"x":128,"y":129,"fill":123,"style":130},"24","32","font-size:16px;font-weight:700","The box either changes size, or it does not",[126,133,138],{"x":134,"y":135,"fill":123,"style":136,"textAnchor":137},"120","60","font-size:11.5px;font-weight:700;","middle","No reservation",[101,140],{"x":141,"y":142,"width":143,"height":144,"rx":145,"fill":123,"style":146},"40","70","160","26","4","fill-opacity:0.14",[126,148,151],{"x":134,"y":149,"fill":123,"style":150,"textAnchor":137},"88","font-size:10px;","iframe 300×150",[101,153],{"x":141,"y":154,"width":143,"height":155,"rx":145,"fill":156,"stroke":156,"style":157},"102","34","#0466c8","fill-opacity:0.16;stroke-opacity:0.5",[126,159,161],{"x":134,"y":160,"fill":123,"style":150,"textAnchor":137},"124","next paragraph",[126,163,167],{"x":164,"y":165,"fill":123,"style":166},"230","100","font-size:11px;fill-opacity:0.7","loads…",[169,170],"line",{"x1":171,"y1":172,"x2":173,"y2":172,"stroke":123,"strokeDashArray":174,"style":176},"210","106","272",[145,175],"3","stroke-opacity:0.45",[178,179],"polygon",{"points":180,"fill":123,"style":181},"280,106 270,101 270,111","fill-opacity:0.55",[101,183],{"x":184,"y":142,"width":143,"height":185,"rx":145,"fill":186,"stroke":187,"style":188},"290","76","#ffc300","#b8860b","fill-opacity:0.3",[126,190,193],{"x":191,"y":192,"fill":123,"style":150,"textAnchor":137},"370","112","iframe 300×400",[101,195],{"x":184,"y":196,"width":143,"height":155,"rx":145,"fill":156,"stroke":156,"style":157},"152",[126,198,161],{"x":191,"y":199,"fill":123,"style":150,"textAnchor":137},"174",[126,201,204],{"x":191,"y":202,"fill":187,"style":203,"textAnchor":137},"208","font-size:11px;font-weight:700;","pushed down 250px",[169,206],{"x1":207,"y1":208,"x2":207,"y2":164,"stroke":123,"style":209},"490","50","stroke-opacity:0.2",[126,211,213],{"x":212,"y":135,"fill":123,"style":136,"textAnchor":137},"620","aspect-ratio reserved",[101,215],{"x":216,"y":142,"width":143,"height":185,"rx":145,"fill":123,"stroke":123,"strokeDashArray":217,"style":219},"540",[218,145],"5","fill-opacity:0.04;stroke-opacity:0.45",[126,221,223],{"x":212,"y":222,"fill":123,"style":150,"textAnchor":137},"105","reserved 16:9",[126,225,227],{"x":212,"y":226,"fill":123,"style":150,"textAnchor":137},"122","before load",[101,229],{"x":216,"y":196,"width":143,"height":155,"rx":145,"fill":156,"stroke":156,"style":157},[126,231,161],{"x":212,"y":199,"fill":123,"style":150,"textAnchor":137},[126,233,234],{"x":212,"y":202,"fill":123,"style":203,"textAnchor":137},"nothing moves — 0.00 CLS",[54,236,238],{"id":237},"root-cause-analysis","Root Cause Analysis",[15,240,241,244,245,248,249,252,253,255],{},[65,242,243],{},"1. Iframes have a default size."," With no ",[30,246,247],{},"width","\u002F",[30,250,251],{},"height"," attributes and no CSS, an iframe is ",[30,254,36],{},". Whatever the embedded content turns out to be, the initial layout is wrong and correcting it is a shift.",[15,257,258,107,261,264],{},[65,259,260],{},"2. Percentage widths do not imply a height.",[30,262,263],{},"width: 100%"," with no height still leaves the browser guessing, so a responsive embed resizes vertically even when its width was correct from the start.",[15,266,267,270],{},[65,268,269],{},"3. Self-resizing widgets shift twice."," A widget that measures its content and posts a new height to the parent produces a second shift long after load — and because it is not within 500ms of a user interaction, it counts in full.",[15,272,273,276],{},[65,274,275],{},"4. Variable-height content genuinely has no known ratio."," A comment thread or a social post can be any height. There is no ratio to reserve, so the solution has to be a sensible minimum plus a scroll container, not a fixed aspect.",[54,278,280],{"id":279},"step-by-step-resolution","Step-by-Step Resolution",[15,282,283,289],{},[65,284,285,286,288],{},"1. Use ",[30,287,47],{}," for anything with a known shape."," Video, maps and most media embeds have a fixed ratio.",[291,292,297],"pre",{"className":293,"code":294,"language":295,"meta":296,"style":296},"language-css shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast",".embed {\n  width: 100%;\n  aspect-ratio: 16 \u002F 9;      \u002F* the box exists before the iframe loads *\u002F\n  border: 0;\n  display: block;\n}\n","css","",[30,298,299,311,329,353,365,378],{"__ignoreMap":296},[300,301,303,307],"span",{"class":169,"line":302},1,[300,304,306],{"class":305},"sPXB4",".embed",[300,308,310],{"class":309},"saISM"," {\n",[300,312,314,317,320,322,326],{"class":169,"line":313},2,[300,315,316],{"class":305},"  width",[300,318,319],{"class":309},": ",[300,321,165],{"class":305},[300,323,325],{"class":324},"sPARh","%",[300,327,328],{"class":309},";\n",[300,330,332,335,337,340,343,346,349],{"class":169,"line":331},3,[300,333,334],{"class":305},"  aspect-ratio",[300,336,319],{"class":309},[300,338,339],{"class":305},"16",[300,341,342],{"class":309}," \u002F ",[300,344,345],{"class":305},"9",[300,347,348],{"class":309},";      ",[300,350,352],{"class":351},"sjfSM","\u002F* the box exists before the iframe loads *\u002F\n",[300,354,356,359,361,363],{"class":169,"line":355},4,[300,357,358],{"class":305},"  border",[300,360,319],{"class":309},[300,362,105],{"class":305},[300,364,328],{"class":309},[300,366,368,371,373,376],{"class":169,"line":367},5,[300,369,370],{"class":305},"  display",[300,372,319],{"class":309},[300,374,375],{"class":305},"block",[300,377,328],{"class":309},[300,379,381],{"class":169,"line":380},6,[300,382,383],{"class":309},"}\n",[291,385,389],{"className":386,"code":387,"language":388,"meta":296,"style":296},"language-html shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","\u003Ciframe class=\"embed\" src=\"https:\u002F\u002Fplayer.example.com\u002Fv\u002Fabc\" title=\"Product demo\"\n        loading=\"lazy\" allowfullscreen>\u003C\u002Fiframe>\n\u003C!-- trade-off: aspect-ratio guarantees no shift only if the ratio is right.\n     A 4:3 source inside a 16:9 box letterboxes rather than shifts — visually\n     imperfect but metrically clean. Match the ratio to the source. -->\n","html",[30,390,391,426,447,452,457],{"__ignoreMap":296},[300,392,393,396,400,403,406,410,413,415,418,421,423],{"class":169,"line":302},[300,394,395],{"class":309},"\u003C",[300,397,399],{"class":398},"sZBmE","iframe",[300,401,402],{"class":305}," class",[300,404,405],{"class":309},"=",[300,407,409],{"class":408},"sZ8jY","\"embed\"",[300,411,412],{"class":305}," src",[300,414,405],{"class":309},[300,416,417],{"class":408},"\"https:\u002F\u002Fplayer.example.com\u002Fv\u002Fabc\"",[300,419,420],{"class":305}," title",[300,422,405],{"class":309},[300,424,425],{"class":408},"\"Product demo\"\n",[300,427,428,431,433,436,439,442,444],{"class":169,"line":313},[300,429,430],{"class":305},"        loading",[300,432,405],{"class":309},[300,434,435],{"class":408},"\"lazy\"",[300,437,438],{"class":305}," allowfullscreen",[300,440,441],{"class":309},">\u003C\u002F",[300,443,399],{"class":398},[300,445,446],{"class":309},">\n",[300,448,449],{"class":169,"line":331},[300,450,451],{"class":351},"\u003C!-- trade-off: aspect-ratio guarantees no shift only if the ratio is right.\n",[300,453,454],{"class":169,"line":355},[300,455,456],{"class":351},"     A 4:3 source inside a 16:9 box letterboxes rather than shifts — visually\n",[300,458,459],{"class":169,"line":367},[300,460,461],{"class":351},"     imperfect but metrically clean. Match the ratio to the source. -->\n",[15,463,464],{},"Expected outcome: the embed's contribution to CLS drops to zero, regardless of when it loads.",[15,466,467,470],{},[65,468,469],{},"2. Keep the width and height attributes as well."," They give the browser the ratio even before CSS is applied, which matters if your stylesheet is deferred.",[291,472,474],{"className":386,"code":473,"language":388,"meta":296,"style":296},"\u003Ciframe src=\"...\" width=\"1280\" height=\"720\" style=\"width:100%;height:auto\"\n        title=\"Product demo\" loading=\"lazy\">\u003C\u002Fiframe>\n\u003C!-- trade-off: the inline style is needed because the attributes alone set a\n     fixed pixel size. Attributes for the ratio, CSS for the responsiveness. -->\n",[30,475,476,513,536,541],{"__ignoreMap":296},[300,477,478,480,482,484,486,489,492,494,497,500,502,505,508,510],{"class":169,"line":302},[300,479,395],{"class":309},[300,481,399],{"class":398},[300,483,412],{"class":305},[300,485,405],{"class":309},[300,487,488],{"class":408},"\"...\"",[300,490,491],{"class":305}," width",[300,493,405],{"class":309},[300,495,496],{"class":408},"\"1280\"",[300,498,499],{"class":305}," height",[300,501,405],{"class":309},[300,503,504],{"class":408},"\"720\"",[300,506,507],{"class":305}," style",[300,509,405],{"class":309},[300,511,512],{"class":408},"\"width:100%;height:auto\"\n",[300,514,515,518,520,523,526,528,530,532,534],{"class":169,"line":313},[300,516,517],{"class":305},"        title",[300,519,405],{"class":309},[300,521,522],{"class":408},"\"Product demo\"",[300,524,525],{"class":305}," loading",[300,527,405],{"class":309},[300,529,435],{"class":408},[300,531,441],{"class":309},[300,533,399],{"class":398},[300,535,446],{"class":309},[300,537,538],{"class":169,"line":331},[300,539,540],{"class":351},"\u003C!-- trade-off: the inline style is needed because the attributes alone set a\n",[300,542,543],{"class":169,"line":355},[300,544,545],{"class":351},"     fixed pixel size. Attributes for the ratio, CSS for the responsiveness. -->\n",[15,547,548,551],{},[65,549,550],{},"3. For unknown-height embeds, reserve a measured minimum."," Take the median rendered height from real pages rather than guessing.",[291,553,555],{"className":293,"code":554,"language":295,"meta":296,"style":296},".embed-social {\n  width: 100%;\n  min-height: 480px;          \u002F* p50 rendered height, measured across 50 posts *\u002F\n  contain: layout;            \u002F* changes inside cannot reflow the page around it *\u002F\n}\n\u002F* trade-off: a minimum that is too generous leaves visible whitespace under\n   short embeds; too small and tall ones still shift. Measure, then round up\n   slightly — whitespace is cheaper than a layout shift. *\u002F\n",[30,556,557,564,576,595,606,610,615,621],{"__ignoreMap":296},[300,558,559,562],{"class":169,"line":302},[300,560,561],{"class":305},".embed-social",[300,563,310],{"class":309},[300,565,566,568,570,572,574],{"class":169,"line":313},[300,567,316],{"class":305},[300,569,319],{"class":309},[300,571,165],{"class":305},[300,573,325],{"class":324},[300,575,328],{"class":309},[300,577,578,581,583,586,589,592],{"class":169,"line":331},[300,579,580],{"class":305},"  min-height",[300,582,319],{"class":309},[300,584,585],{"class":305},"480",[300,587,588],{"class":324},"px",[300,590,591],{"class":309},";          ",[300,593,594],{"class":351},"\u002F* p50 rendered height, measured across 50 posts *\u002F\n",[300,596,597,600,603],{"class":169,"line":355},[300,598,599],{"class":305},"  contain",[300,601,602],{"class":309},": layout;            ",[300,604,605],{"class":351},"\u002F* changes inside cannot reflow the page around it *\u002F\n",[300,607,608],{"class":169,"line":367},[300,609,383],{"class":309},[300,611,612],{"class":169,"line":380},[300,613,614],{"class":351},"\u002F* trade-off: a minimum that is too generous leaves visible whitespace under\n",[300,616,618],{"class":169,"line":617},7,[300,619,620],{"class":351},"   short embeds; too small and tall ones still shift. Measure, then round up\n",[300,622,624],{"class":169,"line":623},8,[300,625,626],{"class":351},"   slightly — whitespace is cheaper than a layout shift. *\u002F\n",[15,628,629],{},"Expected outcome: most instances never resize; the remainder shift by a much smaller distance, so the CLS contribution falls by roughly the ratio of the error.",[15,631,632,635,636,640],{},[65,633,634],{},"4. Handle self-resizing widgets explicitly."," If a widget posts its height, apply it ",[637,638,639],"em",{},"before"," first paint where possible, or accept the first size and disallow later growth.",[291,642,646],{"className":643,"code":644,"language":645,"meta":296,"style":296},"language-javascript shiki shiki-themes github-light-high-contrast github-dark-high-contrast github-light-high-contrast","addEventListener('message', (e) => {\n  if (e.origin !== 'https:\u002F\u002Fwidget.example.com') return;\n  const frame = document.querySelector('#widget');\n  if (!frame || typeof e.data?.height !== 'number') return;\n  \u002F\u002F Only ever grow the reserved box if the widget is off-screen.\n  const onScreen = frame.getBoundingClientRect().top \u003C innerHeight;\n  if (!onScreen) frame.style.height = `${e.data.height}px`;\n}, false);\n\u002F\u002F trade-off: refusing to resize an on-screen widget can clip its content.\n\u002F\u002F Pair it with overflow scrolling inside the reserved box so nothing is lost —\n\u002F\u002F a scrollbar is a better outcome than moving the article the user is reading.\n","javascript",[30,647,648,675,696,721,754,759,782,815,825,831,837],{"__ignoreMap":296},[300,649,650,654,657,660,663,667,670,673],{"class":169,"line":302},[300,651,653],{"class":652},"smZ65","addEventListener",[300,655,656],{"class":309},"(",[300,658,659],{"class":408},"'message'",[300,661,662],{"class":309},", (",[300,664,666],{"class":665},"sQw3B","e",[300,668,669],{"class":309},") ",[300,671,672],{"class":324},"=>",[300,674,310],{"class":309},[300,676,677,680,683,686,689,691,694],{"class":169,"line":313},[300,678,679],{"class":324},"  if",[300,681,682],{"class":309}," (e.origin ",[300,684,685],{"class":324},"!==",[300,687,688],{"class":408}," 'https:\u002F\u002Fwidget.example.com'",[300,690,669],{"class":309},[300,692,693],{"class":324},"return",[300,695,328],{"class":309},[300,697,698,701,704,707,710,713,715,718],{"class":169,"line":331},[300,699,700],{"class":324},"  const",[300,702,703],{"class":305}," frame",[300,705,706],{"class":324}," =",[300,708,709],{"class":309}," document.",[300,711,712],{"class":652},"querySelector",[300,714,656],{"class":309},[300,716,717],{"class":408},"'#widget'",[300,719,720],{"class":309},");\n",[300,722,723,725,728,731,734,737,740,743,745,748,750,752],{"class":169,"line":355},[300,724,679],{"class":324},[300,726,727],{"class":309}," (",[300,729,730],{"class":324},"!",[300,732,733],{"class":309},"frame ",[300,735,736],{"class":324},"||",[300,738,739],{"class":324}," typeof",[300,741,742],{"class":309}," e.data?.height ",[300,744,685],{"class":324},[300,746,747],{"class":408}," 'number'",[300,749,669],{"class":309},[300,751,693],{"class":324},[300,753,328],{"class":309},[300,755,756],{"class":169,"line":367},[300,757,758],{"class":351},"  \u002F\u002F Only ever grow the reserved box if the widget is off-screen.\n",[300,760,761,763,766,768,771,774,777,779],{"class":169,"line":380},[300,762,700],{"class":324},[300,764,765],{"class":305}," onScreen",[300,767,706],{"class":324},[300,769,770],{"class":309}," frame.",[300,772,773],{"class":652},"getBoundingClientRect",[300,775,776],{"class":309},"().top ",[300,778,395],{"class":324},[300,780,781],{"class":309}," innerHeight;\n",[300,783,784,786,788,790,793,795,798,800,803,806,808,810,813],{"class":169,"line":617},[300,785,679],{"class":324},[300,787,727],{"class":309},[300,789,730],{"class":324},[300,791,792],{"class":309},"onScreen) frame.style.height ",[300,794,405],{"class":324},[300,796,797],{"class":408}," `${",[300,799,666],{"class":309},[300,801,802],{"class":408},".",[300,804,805],{"class":309},"data",[300,807,802],{"class":408},[300,809,251],{"class":309},[300,811,812],{"class":408},"}px`",[300,814,328],{"class":309},[300,816,817,820,823],{"class":169,"line":623},[300,818,819],{"class":309},"}, ",[300,821,822],{"class":305},"false",[300,824,720],{"class":309},[300,826,828],{"class":169,"line":827},9,[300,829,830],{"class":351},"\u002F\u002F trade-off: refusing to resize an on-screen widget can clip its content.\n",[300,832,834],{"class":169,"line":833},10,[300,835,836],{"class":351},"\u002F\u002F Pair it with overflow scrolling inside the reserved box so nothing is lost —\n",[300,838,840],{"class":169,"line":839},11,[300,841,842],{"class":351},"\u002F\u002F a scrollbar is a better outcome than moving the article the user is reading.\n",[54,844,846],{"id":845},"verification","Verification",[15,848,849],{},"Reload with Layout Shift Regions enabled and confirm no highlight appears over or below any embed, on both a phone-sized and a desktop-sized viewport — a ratio that works at one width can be wrong at another if the embed has a fixed minimum.",[15,851,852],{},"Quantify the change with the same observer you use for other shift work:",[291,854,856],{"className":643,"code":855,"language":645,"meta":296,"style":296},"let cls = 0;\nnew PerformanceObserver((l) => {\n  for (const e of l.getEntries()) {\n    if (e.hadRecentInput) continue;\n    cls += e.value;\n    for (const s of e.sources ?? []) {\n      if (s.node?.tagName === 'IFRAME') console.log('iframe shift', e.value.toFixed(4));\n    }\n  }\n}).observe({ type: 'layout-shift', buffered: true });\n",[30,857,858,873,893,918,931,942,965,1003,1008,1013],{"__ignoreMap":296},[300,859,860,863,866,868,871],{"class":169,"line":302},[300,861,862],{"class":324},"let",[300,864,865],{"class":309}," cls ",[300,867,405],{"class":324},[300,869,870],{"class":305}," 0",[300,872,328],{"class":309},[300,874,875,878,881,884,887,889,891],{"class":169,"line":313},[300,876,877],{"class":324},"new",[300,879,880],{"class":652}," PerformanceObserver",[300,882,883],{"class":309},"((",[300,885,886],{"class":665},"l",[300,888,669],{"class":309},[300,890,672],{"class":324},[300,892,310],{"class":309},[300,894,895,898,900,903,906,909,912,915],{"class":169,"line":331},[300,896,897],{"class":324},"  for",[300,899,727],{"class":309},[300,901,902],{"class":324},"const",[300,904,905],{"class":305}," e",[300,907,908],{"class":324}," of",[300,910,911],{"class":309}," l.",[300,913,914],{"class":652},"getEntries",[300,916,917],{"class":309},"()) {\n",[300,919,920,923,926,929],{"class":169,"line":355},[300,921,922],{"class":324},"    if",[300,924,925],{"class":309}," (e.hadRecentInput) ",[300,927,928],{"class":324},"continue",[300,930,328],{"class":309},[300,932,933,936,939],{"class":169,"line":367},[300,934,935],{"class":309},"    cls ",[300,937,938],{"class":324},"+=",[300,940,941],{"class":309}," e.value;\n",[300,943,944,947,949,951,954,956,959,962],{"class":169,"line":380},[300,945,946],{"class":324},"    for",[300,948,727],{"class":309},[300,950,902],{"class":324},[300,952,953],{"class":305}," s",[300,955,908],{"class":324},[300,957,958],{"class":309}," e.sources ",[300,960,961],{"class":324},"??",[300,963,964],{"class":309}," []) {\n",[300,966,967,970,973,976,979,982,985,987,990,993,996,998,1000],{"class":169,"line":617},[300,968,969],{"class":324},"      if",[300,971,972],{"class":309}," (s.node?.tagName ",[300,974,975],{"class":324},"===",[300,977,978],{"class":408}," 'IFRAME'",[300,980,981],{"class":309},") console.",[300,983,984],{"class":652},"log",[300,986,656],{"class":309},[300,988,989],{"class":408},"'iframe shift'",[300,991,992],{"class":309},", e.value.",[300,994,995],{"class":652},"toFixed",[300,997,656],{"class":309},[300,999,145],{"class":305},[300,1001,1002],{"class":309},"));\n",[300,1004,1005],{"class":169,"line":623},[300,1006,1007],{"class":309},"    }\n",[300,1009,1010],{"class":169,"line":827},[300,1011,1012],{"class":309},"  }\n",[300,1014,1015,1018,1021,1024,1027,1030,1033],{"class":169,"line":833},[300,1016,1017],{"class":309},"}).",[300,1019,1020],{"class":652},"observe",[300,1022,1023],{"class":309},"({ type: ",[300,1025,1026],{"class":408},"'layout-shift'",[300,1028,1029],{"class":309},", buffered: ",[300,1031,1032],{"class":305},"true",[300,1034,1035],{"class":309}," });\n",[15,1037,1038],{},"Then keep it from regressing. Embeds are usually added by editors rather than engineers, so the guard belongs in the content pipeline: reject or auto-wrap any embed markup that lacks dimensions, and assert a CLS budget in CI so a template change cannot quietly reintroduce the problem.",[54,1040,1042],{"id":1041},"faq","FAQ",[1044,1045,1048,1052],"details",{"className":1046},[1047],"faq-item",[1049,1050,1051],"summary",{},"Is the padding-top percentage hack still needed?",[15,1053,1054,1055,1057],{},"No. ",[30,1056,47],{}," is supported across all current browsers and expresses the intent directly, without the wrapper element and absolute positioning the old technique required. Keep the hack only if you must support a legacy engine, and then apply it as a fallback beneath the modern declaration rather than instead of it.",[1044,1059,1061,1064],{"className":1060},[1047],[1049,1062,1063],{},"Does lazy-loading an iframe cause layout shift?",[15,1065,1066,1067,1070,1071,1074,1075,1077,1078,1081],{},"Not by itself — provided the box is reserved. ",[30,1068,1069],{},"loading=\"lazy\""," changes ",[637,1072,1073],{},"when"," the embedded document loads, not how much space it occupies, so an embed with a correct ",[30,1076,47],{}," will never shift regardless of load timing. Lazy loading an ",[637,1079,1080],{},"unreserved"," embed makes things worse, because the resize now happens during scrolling, when it is most noticeable.",[1044,1083,1085,1088],{"className":1084},[1047],[1049,1086,1087],{},"What about embeds whose height depends on the viewport?",[15,1089,1090,1091,1094,1095,1098,1099,1102],{},"Use a ratio that is itself responsive: a media query can set ",[30,1092,1093],{},"aspect-ratio: 4 \u002F 3"," on narrow screens and ",[30,1096,1097],{},"16 \u002F 9"," on wide ones, and because the value is applied before load there is still no shift. What you must avoid is switching the ratio ",[637,1100,1101],{},"after"," the embed has rendered, which is a shift you created yourself.",[1044,1104,1106,1109],{"className":1105},[1047],[1049,1107,1108],{},"How do I reserve space for content whose height I genuinely cannot predict?",[15,1110,1111,1112,1114,1115,1118],{},"Give it a measured minimum and let it scroll inside that box rather than growing the page. A comment thread or a feed can be given the median rendered height as ",[30,1113,51],{}," and ",[30,1116,1117],{},"overflow-y: auto",", so late content changes what is inside the box without changing the box. That is a deliberate design decision — a scrollbar inside a section is a visible cost — but it is a smaller cost than moving the article a reader is part-way through.",[1044,1120,1122,1125],{"className":1121},[1047],[1049,1123,1124],{},"Does content-visibility interact with reserved space?",[15,1126,1127,1128,1131,1132,1135],{},"Yes, and the pairing matters. ",[30,1129,1130],{},"content-visibility: auto"," skips rendering off-screen sections, which is a real rendering win on long pages, but the browser then needs ",[30,1133,1134],{},"contain-intrinsic-size"," to know how much room to leave. Without it the skipped section collapses and the scrollbar jumps as the user scrolls — the same shift problem in a different costume. Always set an intrinsic size alongside it.",[1044,1137,1139,1142],{"className":1138},[1047],[1049,1140,1141],{},"Should I reserve space for content that only some users see?",[15,1143,1144],{},"Reserve it only if it renders above the fold, and prefer not rendering it at all otherwise. A consent banner, a promotional strip or a personalised message that appears after a check will shift everything below it if it is injected into the flow. The stable options are to render it in a fixed overlay outside the document flow, to reserve its box unconditionally and accept the whitespace, or to decide server-side so it is present in the first paint.",[54,1146,1148],{"id":1147},"related","Related",[59,1150,1151,1156,1163],{},[62,1152,1153,1155],{},[19,1154,22],{"href":21}," — the full shift taxonomy this case belongs to.",[62,1157,1158,1162],{},[19,1159,1161],{"href":1160},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Fdebugging-cls-caused-by-dynamic-ad-injection\u002F","Debugging CLS caused by dynamic ad injection"," — the harder version of the same problem, where the size is unknown by design.",[62,1164,1165,1169],{},[19,1166,1168],{"href":1167},"\u002Fjavascript-bundle-optimization-code-splitting\u002Fthird-party-script-performance\u002Freplacing-youtube-embeds-with-a-facade\u002F","Replacing YouTube embeds with a facade"," — remove the embed's cost entirely while keeping the reserved box.",[1171,1172,1174],"script",{"type":1173},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Reserve space for embeds to eliminate layout shift\",\n  \"description\": \"Give iframes and third-party widgets their final geometry before they load using aspect-ratio, dimension attributes, measured minimum heights and controlled resize handling.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Apply aspect-ratio\", \"text\": \"Set a width and aspect-ratio on embeds with a known shape so the box exists before the iframe loads.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Keep dimension attributes\", \"text\": \"Retain width and height attributes so the ratio is known even before CSS is applied.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Reserve a measured minimum\", \"text\": \"For unknown-height embeds, set a min-height taken from real measurements and contain layout.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F#step-by-step-resolution\" },\n    { \"@type\": \"HowToStep\", \"position\": 4, \"name\": \"Control self-resizing widgets\", \"text\": \"Only apply a posted height while the widget is off-screen, and scroll inside the reserved box otherwise.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F#step-by-step-resolution\" }\n  ]\n}\n",[1171,1176,1177],{"type":1173},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"How to Reserve Space for Embeds with aspect-ratio\",\n  \"description\": \"Stop iframes and third-party widgets from resizing after load by giving them their final geometry up front.\",\n  \"datePublished\": \"2026-08-01\",\n  \"dateModified\": \"2026-08-01\",\n  \"author\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"publisher\": { \"@type\": \"Organization\", \"name\": \"frontend-performance.com\" },\n  \"mainEntityOfPage\": { \"@type\": \"WebPage\", \"@id\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F\" }\n}\n",[1171,1179,1180],{"type":1173},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"BreadcrumbList\",\n  \"itemListElement\": [\n    { \"@type\": \"ListItem\", \"position\": 1, \"name\": \"Home\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 2, \"name\": \"Core Web Vitals & Measurement\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Reducing Cumulative Layout Shift (CLS)\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Reserving Space for Embeds with aspect-ratio\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F\" }\n  ]\n}\n",[1182,1183,1184],"style",{},"html pre.shiki code .sPXB4, html code.shiki .sPXB4{--shiki-default:#023B95;--shiki-dark:#91CBFF;--shiki-light:#023B95}html pre.shiki code .saISM, html code.shiki .saISM{--shiki-default:#0E1116;--shiki-dark:#F0F3F6;--shiki-light:#0E1116}html pre.shiki code .sPARh, html code.shiki .sPARh{--shiki-default:#A0111F;--shiki-dark:#FF9492;--shiki-light:#A0111F}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 .sZBmE, html code.shiki .sZBmE{--shiki-default:#024C1A;--shiki-dark:#72F088;--shiki-light:#024C1A}html pre.shiki code .sZ8jY, html code.shiki .sZ8jY{--shiki-default:#032563;--shiki-dark:#ADDCFF;--shiki-light:#032563}html pre.shiki code .smZ65, html code.shiki .smZ65{--shiki-default:#622CBC;--shiki-dark:#DBB7FF;--shiki-light:#622CBC}html pre.shiki code .sQw3B, html code.shiki .sQw3B{--shiki-default:#702C00;--shiki-dark:#FFB757;--shiki-light:#702C00}",{"title":296,"searchDepth":313,"depth":313,"links":1186},[1187,1188,1189,1190,1191,1192],{"id":56,"depth":313,"text":57},{"id":237,"depth":313,"text":238},{"id":279,"depth":313,"text":280},{"id":845,"depth":313,"text":846},{"id":1041,"depth":313,"text":1042},{"id":1147,"depth":313,"text":1148},"Stop late-loading iframes and widgets from moving the page: reserve the exact box up front, and handle the embeds whose height you cannot know.","md",{"slug":1196,"type":1197,"breadcrumb":1198,"datePublished":1205,"dateModified":1205},"reserving-space-for-embeds-with-aspect-ratio","article",[1199,1201,1202,1203],{"name":1200,"url":248},"Home",{"name":27,"url":26},{"name":22,"url":21},{"name":5,"url":1204},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002F","2026-08-01",true,"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio",{"title":5,"description":1209},"Iframes, videos and third-party widgets collapse to a default box then resize. Reserve the geometry with aspect-ratio and min-height so late content cannot shift the page.","core-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Freserving-space-for-embeds-with-aspect-ratio\u002Findex","4zQ1BEvWpiIBxC940jc4Namkn9uOpLTnxpF69Hk8IPU",[1213,1217],{"title":1214,"path":1215,"stem":1216},"Debugging CLS Caused by Dynamic Ad Injection","\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Fdebugging-cls-caused-by-dynamic-ad-injection","core-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002Fdebugging-cls-caused-by-dynamic-ad-injection\u002Findex",{"title":1218,"path":1219,"stem":1220},"RUM Beacons & Field Data Collection","\u002Fcore-web-vitals-measurement\u002Frum-beacons-and-field-data-collection","core-web-vitals-measurement\u002Frum-beacons-and-field-data-collection\u002Findex",1785622407287]