[{"data":1,"prerenderedAt":941},["ShallowReactive",2],{"content:\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer":3,"surroundings:\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer":932},{"id":4,"title":5,"body":6,"description":912,"extension":913,"meta":914,"navigation":926,"path":927,"seo":928,"stem":930,"__hash__":931},"content\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002Findex.md","Native Lazy Loading vs IntersectionObserver",{"type":7,"value":8,"toc":903},"minimark",[9,14,43,147,152,166,195,202,206,351,358,378,382,394,527,531,558,796,800,810,830,834,849,853,888,893,896,899],[10,11,13],"h1",{"id":12},"native-lazy-loading-vs-intersectionobserver-choosing-a-deferral-mechanism","Native Lazy Loading vs IntersectionObserver: Choosing a Deferral Mechanism",[15,16,17,18,23,24,28,29,33,34,37,38,42],"p",{},"When you defer offscreen media as part of the ",[19,20,22],"a",{"href":21},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F","lazy loading without hurting LCP"," workflow inside the ",[19,25,27],{"href":26},"\u002Fimage-media-optimization\u002F","Image & Media Optimization"," discipline, you have two real options: the native ",[30,31,32],"code",{},"loading=\"lazy\""," attribute or a hand-built ",[30,35,36],{},"IntersectionObserver",". They solve the same surface problem — don't fetch what the user can't see — but they differ sharply in control, predictability, and cost. Picking wrong means either shipping unnecessary JavaScript for behavior the browser gives free, or fighting a black box that won't tune. This page lays out a decision matrix and a clear default. Neither tool should ever touch the ",[19,39,41],{"href":40},"\u002Fcore-web-vitals-measurement\u002Fmeasuring-lcp-with-chrome-devtools\u002F","Largest Contentful Paint"," element, which always loads eagerly.",[15,44,45],{},[46,47,54,55,54,59,54,63,54,73,54,80,54,86,54,90,54,96,54,102,54,106,54,109,54,113,54,116,54,119,54,123,54,126,54,129,54,133,54,136,54,139,54,142,54],"svg",{"xmlns":48,"viewBox":49,"width":50,"role":51,"ariaLabel":52,"style":53},"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","0 0 760 320","100%","img","Comparison matrix of native lazy loading versus IntersectionObserver across control, threshold tuning, bundle cost, and reliability","height:auto;max-width:760px;display:block;margin:1.75rem auto;font-family:inherit;color:#001d3d"," ",[56,57,58],"title",{},"Native lazy vs IntersectionObserver",[60,61,62],"desc",{},"Native loading lazy wins on bundle cost and reliability; IntersectionObserver wins on threshold control and deferring non-image work.",[64,65],"rect",{"x":66,"y":66,"width":67,"height":68,"rx":69,"fill":70,"stroke":71,"style":72},"1","758","318","10","none","currentColor","stroke-opacity:0.18",[74,75,79],"text",{"x":76,"y":77,"fill":71,"style":78},"24","38","font-size:18px;font-weight:700","Pick by what you need to control",[74,81,85],{"x":82,"y":83,"fill":71,"style":84},"300","74","font-size:13px;font-weight:700;text-anchor:middle","Native lazy",[74,87,89],{"x":88,"y":83,"fill":71,"style":84},"560","Observer",[91,92],"line",{"x1":76,"y1":93,"x2":94,"y2":93,"stroke":71,"style":95},"86","736","stroke-opacity:0.3",[74,97,101],{"x":98,"y":99,"fill":71,"style":100},"32","120","font-size:13px","Bundle cost",[74,103,105],{"x":82,"y":99,"fill":71,"style":104},"font-size:13px;text-anchor:middle","zero",[74,107,108],{"x":88,"y":99,"fill":71,"style":104},"ships JS",[74,110,112],{"x":98,"y":111,"fill":71,"style":100},"156","Threshold tuning",[74,114,115],{"x":82,"y":111,"fill":71,"style":104},"browser-set",[74,117,118],{"x":88,"y":111,"fill":71,"style":104},"rootMargin",[74,120,122],{"x":98,"y":121,"fill":71,"style":100},"192","Reliability",[74,124,125],{"x":82,"y":121,"fill":71,"style":104},"always runs",[74,127,128],{"x":88,"y":121,"fill":71,"style":104},"needs script",[74,130,132],{"x":98,"y":131,"fill":71,"style":100},"228","Defer non-image work",[74,134,135],{"x":82,"y":131,"fill":71,"style":104},"no",[74,137,138],{"x":88,"y":131,"fill":71,"style":104},"yes",[91,140],{"x1":76,"y1":141,"x2":94,"y2":141,"stroke":71,"style":95},"250",[74,143,146],{"x":76,"y":144,"fill":71,"style":145},"282","font-size:13px;font-weight:600","Default to native; reach for the observer only for control it cannot give.",[148,149,151],"h2",{"id":150},"how-each-mechanism-actually-works","How Each Mechanism Actually Works",[15,153,154,155,157,158,161,162,165],{},"Native ",[30,156,32],{}," is a declarative hint on ",[30,159,160],{},"\u003Cimg>"," and ",[30,163,164],{},"\u003Ciframe>",". The browser's own heuristics decide when to fetch, factoring in viewport proximity, scroll direction, effective connection type, and a built-in load-in distance that engines tune and occasionally change between releases. There is no JavaScript: the behavior lives in the rendering engine, runs before and independently of your scripts, and degrades gracefully — an unsupporting browser simply loads eagerly. You give up control in exchange for getting correct, maintenance-free behavior for free.",[15,167,168,170,171,174,175,178,179,182,183,185,186,189,190,194],{},[30,169,36],{}," is an imperative API. You hold the real URL out of ",[30,172,173],{},"src"," (typically in ",[30,176,177],{},"data-src","), create an observer with an explicit ",[30,180,181],{},"root",", ",[30,184,118],{},", and ",[30,187,188],{},"threshold",", observe each element, and swap the URL in when the callback fires. The browser tells you when an element crosses your configured boundary, off the main thread, but ",[191,192,193],"em",{},"you"," own the policy: how far ahead to trigger, what to do on entry, and which elements participate. That control is the entire reason to take on the JavaScript, the maintenance, and the failure mode where a broken or late script means images never load.",[15,196,197,198,201],{},"The distinction that matters for performance work is ",[191,199,200],{},"where the deferral logic executes",". Native loading runs inside the rendering engine during parsing and layout, before your JavaScript has even been fetched, so it participates in the same early pipeline as the preload scanner. An observer runs in user-space JavaScript, which means it cannot begin its work until the bundle has downloaded, parsed, and executed — a chain that on a slow mobile connection can add hundreds of milliseconds before the first deferred image is even eligible to load. For images far down a long page this latency is invisible because the user has not scrolled there yet, but for images just below the fold it can produce a visible pop-in that native loading, running earlier, avoids. This timing difference is the hidden cost that the bundle-size column understates: the observer is not just extra bytes, it is extra bytes that gate when deferral can start.",[148,203,205],{"id":204},"the-decision-matrix","The Decision Matrix",[207,208,209,225],"table",{},[210,211,212],"thead",{},[213,214,215,219,223],"tr",{},[216,217,218],"th",{},"Dimension",[216,220,154,221],{},[30,222,32],{},[216,224,36],{},[226,227,228,242,257,269,287,300,313,325,338],"tbody",{},[213,229,230,236,239],{},[231,232,233],"td",{},[234,235,101],"strong",{},[231,237,238],{},"Zero — no JavaScript shipped",[231,240,241],{},"Ships and maintains a script",[213,243,244,249,252],{},[231,245,246],{},[234,247,248],{},"Trigger distance",[231,250,251],{},"Browser-chosen, not configurable",[231,253,254,255],{},"Fully tunable via ",[30,256,118],{},[213,258,259,263,266],{},[231,260,261],{},[234,262,122],{},[231,264,265],{},"Runs even if your JS fails",[231,267,268],{},"Fails closed if the script doesn't run",[213,270,271,276,282],{},[231,272,273],{},[234,274,275],{},"Preload-scanner visible",[231,277,278,279,281],{},"Yes (URL stays in ",[30,280,173],{},")",[231,283,284,285],{},"No when using ",[30,286,177],{},[213,288,289,294,297],{},[231,290,291],{},[234,292,293],{},"Graceful degradation",[231,295,296],{},"Loads eagerly on old browsers",[231,298,299],{},"Needs a no-JS fallback path",[213,301,302,307,310],{},[231,303,304],{},[234,305,306],{},"Fade-in \u002F entry effects",[231,308,309],{},"Not tied to load event",[231,311,312],{},"Easy to couple to intersection",[213,314,315,319,322],{},[231,316,317],{},[234,318,132],{},[231,320,321],{},"No",[231,323,324],{},"Yes (impressions, widgets, CSS bg)",[213,326,327,332,335],{},[231,328,329],{},[234,330,331],{},"Maintenance surface",[231,333,334],{},"None",[231,336,337],{},"Observer lifecycle, unobserve, cleanup",[213,339,340,345,348],{},[231,341,342],{},[234,343,344],{},"Per-element policy",[231,346,347],{},"Uniform browser policy",[231,349,350],{},"Arbitrary per-element logic",[15,352,353,354,357],{},"The matrix has a clear center of gravity: native loading wins every dimension that is about cost, simplicity, and reliability, while the observer wins every dimension that is about ",[191,355,356],{},"control",". That maps cleanly to a rule — default to native, and only spend the observer's cost when you need a capability native loading structurally cannot provide.",[15,359,360,361,364,365,367,368,370,371,374,375,377],{},"Two rows deserve extra weight because they are where teams most often misjudge the trade. The ",[234,362,363],{},"reliability"," row is the one that bites in production: a native ",[30,366,32],{}," image is a self-contained piece of HTML that the browser will always resolve, whereas an observer-driven image is only as reliable as the script that powers it. A bundle that throws before the observer is wired up, a hydration error that halts execution, or a content blocker that strips the script all leave ",[30,369,177],{}," images permanently blank — a far worse outcome than the over-fetch native loading would have caused. The ",[234,372,373],{},"preload-scanner-visible"," row compounds this: because the observer pattern empties ",[30,376,173],{},", the browser cannot see those URLs during early parsing at all, which removes any chance of opportunistic prioritization and makes the images strictly dependent on the JavaScript timeline. Native loading keeps the URL discoverable even while deferring the fetch, so the browser retains full knowledge of the resource graph.",[148,379,381],{"id":380},"where-native-lazy-loading-wins","Where Native Lazy Loading Wins",[15,383,384,385,387,388,390,391,393],{},"For the overwhelming majority of deferred images — article body figures, grid thumbnails, footer assets, the long scroll of a feed — native ",[30,386,32],{}," is the right answer and adding an observer is pure overhead. It costs zero bytes, the browser tunes the trigger distance better than a fixed margin (it adapts to connection speed, which a hardcoded ",[30,389,118],{}," cannot), and it cannot fail: because the URL stays in ",[30,392,173],{},", the image loads even if your JavaScript bundle errors, never parses, or is blocked. It is also visible to the preload scanner, so the browser can reason about it during early parsing.",[395,396,401],"pre",{"className":397,"code":398,"language":399,"meta":400,"style":400},"language-html shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u003C!-- Native: the correct default for the long list of offscreen images -->\n\u003Cimg src=\"figure-800.jpg\"\n     srcset=\"figure-400.jpg 400w, figure-800.jpg 800w, figure-1200.jpg 1200w\"\n     sizes=\"(max-width: 768px) 100vw, 720px\"\n     width=\"1200\" height=\"675\" alt=\"Latency distribution by region\"\n     loading=\"lazy\" decoding=\"async\">\n\u003C!-- trade-off: you cannot tune how far ahead it triggers. If your design needs\n     images fully decoded a long way before they scroll into view (e.g. fast-flick\n     carousels), the browser's distance may pop images in late — that is the one\n     case to consider the observer instead. -->\n","html","",[30,402,403,411,432,443,454,481,503,509,515,521],{"__ignoreMap":400},[404,405,407],"span",{"class":91,"line":406},1,[404,408,410],{"class":409},"sIIH1","\u003C!-- Native: the correct default for the long list of offscreen images -->\n",[404,412,414,418,421,425,428],{"class":91,"line":413},2,[404,415,417],{"class":416},"syybb","\u003C",[404,419,51],{"class":420},"s-fAs",[404,422,424],{"class":423},"sf6mN"," src",[404,426,427],{"class":416},"=",[404,429,431],{"class":430},"s-_DF","\"figure-800.jpg\"\n",[404,433,435,438,440],{"class":91,"line":434},3,[404,436,437],{"class":423},"     srcset",[404,439,427],{"class":416},[404,441,442],{"class":430},"\"figure-400.jpg 400w, figure-800.jpg 800w, figure-1200.jpg 1200w\"\n",[404,444,446,449,451],{"class":91,"line":445},4,[404,447,448],{"class":423},"     sizes",[404,450,427],{"class":416},[404,452,453],{"class":430},"\"(max-width: 768px) 100vw, 720px\"\n",[404,455,457,460,462,465,468,470,473,476,478],{"class":91,"line":456},5,[404,458,459],{"class":423},"     width",[404,461,427],{"class":416},[404,463,464],{"class":430},"\"1200\"",[404,466,467],{"class":423}," height",[404,469,427],{"class":416},[404,471,472],{"class":430},"\"675\"",[404,474,475],{"class":423}," alt",[404,477,427],{"class":416},[404,479,480],{"class":430},"\"Latency distribution by region\"\n",[404,482,484,487,489,492,495,497,500],{"class":91,"line":483},6,[404,485,486],{"class":423},"     loading",[404,488,427],{"class":416},[404,490,491],{"class":430},"\"lazy\"",[404,493,494],{"class":423}," decoding",[404,496,427],{"class":416},[404,498,499],{"class":430},"\"async\"",[404,501,502],{"class":416},">\n",[404,504,506],{"class":91,"line":505},7,[404,507,508],{"class":409},"\u003C!-- trade-off: you cannot tune how far ahead it triggers. If your design needs\n",[404,510,512],{"class":91,"line":511},8,[404,513,514],{"class":409},"     images fully decoded a long way before they scroll into view (e.g. fast-flick\n",[404,516,518],{"class":91,"line":517},9,[404,519,520],{"class":409},"     carousels), the browser's distance may pop images in late — that is the one\n",[404,522,524],{"class":91,"line":523},10,[404,525,526],{"class":409},"     case to consider the observer instead. -->\n",[148,528,530],{"id":529},"where-intersectionobserver-wins","Where IntersectionObserver Wins",[15,532,533,534,537,538,540,541,544,545,548,549,552,553,161,555,557],{},"Reach for the observer only when you need control native loading does not expose. There are four real cases. ",[234,535,536],{},"Threshold tuning:"," a ",[30,539,118],{}," that starts the fetch a specific distance ahead — useful when you have measured that the browser's default pops images in too late for your scroll speed. ",[234,542,543],{},"Entry-coupled effects:"," fading or animating an image in precisely as it intersects, tied to the same trigger as the load. ",[234,546,547],{},"Deferring non-image work:"," firing an analytics impression, hydrating a heavy widget, or injecting a third-party iframe facade on the same visibility trigger — work that native loading has no concept of. ",[234,550,551],{},"Unified policy across mixed content:"," one consistent deferral system spanning images, background images set in CSS, and components, where native loading only covers ",[30,554,160],{},[30,556,164],{},".",[395,559,563],{"className":560,"code":561,"language":562,"meta":400,"style":400},"language-javascript shiki shiki-themes github-light-high-contrast github-light-high-contrast github-light-high-contrast","\u002F\u002F Observer: justified when you need rootMargin control or to defer non-image work\nconst io = new IntersectionObserver((entries, observer) => {\n  for (const entry of entries) {\n    if (!entry.isIntersecting) continue;\n    const el = entry.target;\n    if (el.dataset.src) el.src = el.dataset.src;     \u002F\u002F image\n    el.dispatchEvent(new CustomEvent('inview'));      \u002F\u002F also trigger analytics\u002Fwidgets\n    observer.unobserve(el);                            \u002F\u002F load once, then release\n  }\n}, { rootMargin: '400px 0px', threshold: 0 });\ndocument.querySelectorAll('[data-src]').forEach((el) => io.observe(el));\n\u002F\u002F trade-off: data-src hides the URL from the preload scanner and makes the image\n\u002F\u002F dependent on this script running. Provide a \u003Cnoscript> fallback or accept that\n\u002F\u002F JS-off users see nothing — and never route the LCP image through this path. -->\n","javascript",[30,564,565,570,610,629,648,661,676,704,718,723,740,778,784,790],{"__ignoreMap":400},[404,566,567],{"class":91,"line":406},[404,568,569],{"class":409},"\u002F\u002F Observer: justified when you need rootMargin control or to defer non-image work\n",[404,571,572,576,579,582,585,589,592,596,598,601,604,607],{"class":91,"line":413},[404,573,575],{"class":574},"sP5qI","const",[404,577,578],{"class":423}," io",[404,580,581],{"class":574}," =",[404,583,584],{"class":574}," new",[404,586,588],{"class":587},"ssM3C"," IntersectionObserver",[404,590,591],{"class":416},"((",[404,593,595],{"class":594},"seIZK","entries",[404,597,182],{"class":416},[404,599,600],{"class":594},"observer",[404,602,603],{"class":416},") ",[404,605,606],{"class":574},"=>",[404,608,609],{"class":416}," {\n",[404,611,612,615,618,620,623,626],{"class":91,"line":434},[404,613,614],{"class":574},"  for",[404,616,617],{"class":416}," (",[404,619,575],{"class":574},[404,621,622],{"class":423}," entry",[404,624,625],{"class":574}," of",[404,627,628],{"class":416}," entries) {\n",[404,630,631,634,636,639,642,645],{"class":91,"line":445},[404,632,633],{"class":574},"    if",[404,635,617],{"class":416},[404,637,638],{"class":574},"!",[404,640,641],{"class":416},"entry.isIntersecting) ",[404,643,644],{"class":574},"continue",[404,646,647],{"class":416},";\n",[404,649,650,653,656,658],{"class":91,"line":456},[404,651,652],{"class":574},"    const",[404,654,655],{"class":423}," el",[404,657,581],{"class":574},[404,659,660],{"class":416}," entry.target;\n",[404,662,663,665,668,670,673],{"class":91,"line":483},[404,664,633],{"class":574},[404,666,667],{"class":416}," (el.dataset.src) el.src ",[404,669,427],{"class":574},[404,671,672],{"class":416}," el.dataset.src;     ",[404,674,675],{"class":409},"\u002F\u002F image\n",[404,677,678,681,684,687,690,693,695,698,701],{"class":91,"line":505},[404,679,680],{"class":416},"    el.",[404,682,683],{"class":587},"dispatchEvent",[404,685,686],{"class":416},"(",[404,688,689],{"class":574},"new",[404,691,692],{"class":587}," CustomEvent",[404,694,686],{"class":416},[404,696,697],{"class":430},"'inview'",[404,699,700],{"class":416},"));      ",[404,702,703],{"class":409},"\u002F\u002F also trigger analytics\u002Fwidgets\n",[404,705,706,709,712,715],{"class":91,"line":511},[404,707,708],{"class":416},"    observer.",[404,710,711],{"class":587},"unobserve",[404,713,714],{"class":416},"(el);                            ",[404,716,717],{"class":409},"\u002F\u002F load once, then release\n",[404,719,720],{"class":91,"line":517},[404,721,722],{"class":416},"  }\n",[404,724,725,728,731,734,737],{"class":91,"line":523},[404,726,727],{"class":416},"}, { rootMargin: ",[404,729,730],{"class":430},"'400px 0px'",[404,732,733],{"class":416},", threshold: ",[404,735,736],{"class":423},"0",[404,738,739],{"class":416}," });\n",[404,741,743,746,749,751,754,757,760,762,765,767,769,772,775],{"class":91,"line":742},11,[404,744,745],{"class":416},"document.",[404,747,748],{"class":587},"querySelectorAll",[404,750,686],{"class":416},[404,752,753],{"class":430},"'[data-src]'",[404,755,756],{"class":416},").",[404,758,759],{"class":587},"forEach",[404,761,591],{"class":416},[404,763,764],{"class":594},"el",[404,766,603],{"class":416},[404,768,606],{"class":574},[404,770,771],{"class":416}," io.",[404,773,774],{"class":587},"observe",[404,776,777],{"class":416},"(el));\n",[404,779,781],{"class":91,"line":780},12,[404,782,783],{"class":409},"\u002F\u002F trade-off: data-src hides the URL from the preload scanner and makes the image\n",[404,785,787],{"class":91,"line":786},13,[404,788,789],{"class":409},"\u002F\u002F dependent on this script running. Provide a \u003Cnoscript> fallback or accept that\n",[404,791,793],{"class":91,"line":792},14,[404,794,795],{"class":409},"\u002F\u002F JS-off users see nothing — and never route the LCP image through this path. -->\n",[148,797,799],{"id":798},"a-hybrid-native-by-default-observer-for-the-exceptions","A Hybrid: Native by Default, Observer for the Exceptions",[15,801,802,803,805,806,557],{},"These are not mutually exclusive. The strongest production setup uses native ",[30,804,32],{}," as the baseline for every offscreen image and reserves the observer for the specific elements that need its control — the fast carousel that needs an early trigger, the analytics impressions, the click-to-load iframe facades. This keeps the bundle small, keeps the common path reliable, and pays the observer's cost only where it buys something. The one element exempt from both is the hero: it loads eagerly regardless, paired with priority hints from ",[19,807,809],{"href":808},"\u002Fimage-media-optimization\u002Fimage-cdns-and-fetchpriority\u002Fusing-fetchpriority-to-prioritize-the-lcp-image\u002F","using fetchpriority to prioritize the LCP image",[15,811,812,813,816,817,820,821,824,825,829],{},"Whichever you choose, both mechanisms share one non-negotiable requirement: reserve the element's box with ",[30,814,815],{},"width","\u002F",[30,818,819],{},"height"," or an ",[30,822,823],{},"aspect-ratio"," so the deferred load does not shift content, which is the classic ",[19,826,828],{"href":827},"\u002Fcore-web-vitals-measurement\u002Freducing-cumulative-layout-shift-cls\u002F","Cumulative Layout Shift"," failure that turns a deferral win into a stability loss.",[148,831,833],{"id":832},"when-to-pick-which-the-short-version","When to Pick Which: The Short Version",[15,835,836,837,842,843,845,846,848],{},"Pick ",[234,838,839,840],{},"native ",[30,841,32],{}," when: the images are ordinary offscreen content, you want zero JavaScript, you need the deferral to work even if scripts fail, and the browser's default trigger distance is acceptable — which is most of the time. Pick ",[234,844,36],{}," when: you have measured that you need a specific ",[30,847,118],{},", you must couple entry to effects or non-image work, or you are building one deferral policy across mixed content types. If you are unsure, choose native — it is the cheaper, safer default, and you can always upgrade specific elements to the observer later. And in both cases, keep the LCP image eager and out of either system.",[148,850,852],{"id":851},"related","Related",[854,855,856,863,870,876,882],"ul",{},[857,858,859,862],"li",{},[19,860,861],{"href":21},"Lazy loading images without hurting LCP"," — the full deferral workflow both mechanisms serve.",[857,864,865,869],{},[19,866,868],{"href":867},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Ffixing-lazy-loaded-images-that-delay-lcp\u002F","Fixing lazy-loaded images that delay LCP"," — what happens when either mechanism wrongly catches the hero.",[857,871,872,875],{},[19,873,874],{"href":808},"Using fetchpriority to prioritize the LCP image"," — handle the one image neither deferral mechanism should touch.",[857,877,878,881],{},[19,879,880],{"href":40},"Measuring LCP with Chrome DevTools"," — confirm your deferral choice did not move the loading metric.",[857,883,884,887],{},[19,885,886],{"href":827},"Reducing Cumulative Layout Shift"," — reserve space so either mechanism loads without shifting content.",[889,890,892],"script",{"type":891},"application\u002Fld+json","\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"TechArticle\",\n  \"headline\": \"Native Lazy Loading vs IntersectionObserver: Choosing a Deferral Mechanism\",\n  \"description\": \"A decision matrix comparing native loading=lazy with a custom IntersectionObserver across control, threshold tuning, bundle cost, and reliability.\",\n  \"datePublished\": \"2026-06-18\",\n  \"dateModified\": \"2026-06-18\",\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\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F\" }\n}\n",[889,894,895],{"type":891},"\n{\n  \"@context\": \"https:\u002F\u002Fschema.org\",\n  \"@type\": \"HowTo\",\n  \"name\": \"Choose between native lazy loading and IntersectionObserver\",\n  \"description\": \"Decide which deferral mechanism to use for offscreen images based on control, cost, and reliability needs.\",\n  \"step\": [\n    { \"@type\": \"HowToStep\", \"position\": 1, \"name\": \"Default to native lazy loading\", \"text\": \"Use loading=lazy with decoding=async for ordinary offscreen images at zero JavaScript cost.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F#where-native-lazy-loading-wins\" },\n    { \"@type\": \"HowToStep\", \"position\": 2, \"name\": \"Use IntersectionObserver for control\", \"text\": \"Reach for an observer only when you need rootMargin tuning, entry effects, or to defer non-image work.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F#where-intersectionobserver-wins\" },\n    { \"@type\": \"HowToStep\", \"position\": 3, \"name\": \"Combine them as a hybrid\", \"text\": \"Use native by default and the observer only for the exceptional elements that need its control.\", \"url\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F#a-hybrid-native-by-default-observer-for-the-exceptions\" }\n  ]\n}\n",[889,897,898],{"type":891},"\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\": \"Image & Media Optimization\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 3, \"name\": \"Lazy Loading Images Without Hurting LCP\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002F\" },\n    { \"@type\": \"ListItem\", \"position\": 4, \"name\": \"Native Lazy Loading vs IntersectionObserver\", \"item\": \"https:\u002F\u002Ffrontend-performance.com\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F\" }\n  ]\n}\n",[900,901,902],"style",{},"html pre.shiki code .sIIH1, html code.shiki .sIIH1{--shiki-default:#66707B;--shiki-dark:#66707B;--shiki-light:#66707B}html pre.shiki code .syybb, html code.shiki .syybb{--shiki-default:#0E1116;--shiki-dark:#0E1116;--shiki-light:#0E1116}html pre.shiki code .s-fAs, html code.shiki .s-fAs{--shiki-default:#024C1A;--shiki-dark:#024C1A;--shiki-light:#024C1A}html pre.shiki code .sf6mN, html code.shiki .sf6mN{--shiki-default:#023B95;--shiki-dark:#023B95;--shiki-light:#023B95}html pre.shiki code .s-_DF, html code.shiki .s-_DF{--shiki-default:#032563;--shiki-dark:#032563;--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);}html pre.shiki code .sP5qI, html code.shiki .sP5qI{--shiki-default:#A0111F;--shiki-dark:#A0111F;--shiki-light:#A0111F}html pre.shiki code .ssM3C, html code.shiki .ssM3C{--shiki-default:#622CBC;--shiki-dark:#622CBC;--shiki-light:#622CBC}html pre.shiki code .seIZK, html code.shiki .seIZK{--shiki-default:#702C00;--shiki-dark:#702C00;--shiki-light:#702C00}",{"title":400,"searchDepth":413,"depth":413,"links":904},[905,906,907,908,909,910,911],{"id":150,"depth":413,"text":151},{"id":204,"depth":413,"text":205},{"id":380,"depth":413,"text":381},{"id":529,"depth":413,"text":530},{"id":798,"depth":413,"text":799},{"id":832,"depth":413,"text":833},{"id":851,"depth":413,"text":852},"A decision matrix comparing native loading=lazy with a custom IntersectionObserver for deferring images, with guidance on when each wins.","md",{"slug":915,"type":916,"breadcrumb":917,"datePublished":925,"dateModified":925},"native-lazy-loading-vs-intersection-observer","long_tail",[918,920,921,923],{"name":919,"url":816},"Home",{"name":27,"url":26},{"name":922,"url":21},"Lazy Loading Images Without Hurting LCP",{"name":5,"url":924},"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002F","2026-06-18",true,"\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer",{"title":5,"description":929},"Decide between loading=lazy and a custom IntersectionObserver: control, threshold tuning, browser behavior, and bundle cost. When to pick each.","image-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Fnative-lazy-loading-vs-intersection-observer\u002Findex","bq3Y-a1fHAzO-i6xXYEP0HB35xM71eiud7G9gF02tKY",[933,937],{"title":934,"path":935,"stem":936,"children":-1},"Fixing Lazy-Loaded Images That Delay LCP","\u002Fimage-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Ffixing-lazy-loaded-images-that-delay-lcp","image-media-optimization\u002Flazy-loading-images-without-hurting-lcp\u002Ffixing-lazy-loaded-images-that-delay-lcp\u002Findex",{"title":938,"path":939,"stem":940,"children":-1},"Responsive Images with srcset and sizes","\u002Fimage-media-optimization\u002Fresponsive-images-with-srcset-and-sizes","image-media-optimization\u002Fresponsive-images-with-srcset-and-sizes\u002Findex",1782237171329]