<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/">
	<channel>
		<title>Make Webpages Load Faster with Lazy Loading | Infinum</title>
		<atom:link href="https://infinum.com/blog/lazy-loading/feed/" rel="self" type="application/rss+xml" />
		<link>https://infinum.com/blog/lazy-loading/</link>
		<description>Building digital products</description>
		<lastBuildDate>Fri, 03 Apr 2026 12:58:20 +0000</lastBuildDate>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>

					<item>
				<image>
					<url>7913https://infinum.com/uploads/2020/11/lazy-loading-0.webp</url>
				</image>
				<title>Make Webpages Load Faster with Lazy Loading</title>
				<link>https://infinum.com/blog/lazy-loading/</link>
				<pubDate>Thu, 29 Oct 2020 16:10:00 +0000</pubDate>
				<dc:creator>Filip Jakov Bulić</dc:creator>
				<guid isPermaLink="false">https://infinum.com/the-capsized-eight/lazy-loading/</guid>
				<description>
					<![CDATA[<p>Don&#8217;t cut down on webpage images – cut down on loading time.</p>
<p>The post <a href="https://infinum.com/blog/lazy-loading/">Make Webpages Load Faster with Lazy Loading</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-379"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-92">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-95"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-93">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-94'
	>
	Media resources like images, videos, and iframes are a critical part of the web. Be it banners, product images, videos, or logos; it is impossible to imagine a website without them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-98"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-96">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-97'
	>
	According to the <a href="https://httparchive.org/reports/page-weight#bytesImg">HTTP Archive’s State of the Web report</a>, images account for more than <strong>50% of the median webpage’s byte size</strong>. At the 90th percentile, sites download about 4.7 MB of images on desktop and mobile. That’s quite a lot of memes!</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-101"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-99">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-100'
	>
	So, it’s not like we can do away with them. Images are essential to the overall user experience, so we need to make our web pages load fast with them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-104"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-102">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-103'
	>
	The concept of lazy loading</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-107"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-105">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-106'
	>
	Lazy loading is a technique that defers the loading of non-critical resources during the page load time. Instead, these non-critical resources are loaded at the moment of need. “Non-critical” is often synonymous with “off-screen”. We can apply lazy loading to about any resource on a page. For example, even a JavaScript file can be held back if it is best not to load it initially.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-110"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-108">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-109'
	>
	This approach is beneficial because:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-113"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-111">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-112'
	>
	<li><strong>It doesn’t waste data</strong>. Users with limited data plans can save data, and users with unlimited data plans can save precious bandwidth for more important resources.</li><li><strong>It saves processing time, battery, and other system resources</strong>. After a media resource is downloaded, the browser must decode it and render its content in the viewport.</li><li><strong>It saves money.</strong> Content Delivery Networks(CDNs) deliver media resources at a cost based on the number of bytes transferred. Reducing the total bytes delivered on the page can save a couple of pennies.</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-118"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="blockquote block-blockquote__blockquote" data-id="es-114">
	
	<div class="blockquote__content">
		<i
	class="icon blockquote__icon icon--size-16 icon--scale-100"
	 aria-hidden='true' data-name='blockquote-24' data-id='es-115'>
	<svg fill='none' height='24' viewBox='0 0 24 24' width='24' xmlns='http://www.w3.org/2000/svg'><path clip-rule='evenodd' d='m12 24c6.6274 0 12-5.3726 12-12 0-2.79685-.9568-5.37021-2.561-7.41062-.581.22951-1.0832.60583-1.5069 1.12898-.5132.60844-.7698 1.41969-.7698 2.43375v.07605h2.5789v5.59004h-5.6197v-5.01962c0-1.11547.154-2.06616.4619-2.85205.3336-.81125.757-1.48307 1.2702-2.01545.528-.52161 1.1175-.92155 1.7687-1.1998-2.0728-1.70651-4.7279-2.73128-7.6223-2.73128-6.62742 0-12 5.37258-12 12 0 6.6274 5.37258 12 12 12zm-3.53811-18.05347c-.30793.78589-.46189 1.73658-.46189 2.85205v5.01962h5.6197v-5.59004h-2.5789v-.07605c0-1.01406.2566-1.82531.7698-2.43375.5389-.63379 1.1804-1.05209 1.9245-1.2549v-2.28164c-.7441.07605-1.4626.25351-2.1555.53238-.6928.27887-1.3086.68449-1.84752 1.21688-.51321.53238-.9366 1.2042-1.27019 2.01545z' fill='currentColor' fill-rule='evenodd'/></svg></i><p	class='typography typography--size-36-text js-typography blockquote__quote'
	data-id='es-116'
	>
	When we lazy load images, videos or iframes, we reduce the initial page load time, initial page weight, and system resource usage, all of which have a positive impact on performance.</p>
		<div class="blockquote__caption-wrap">
					</div>
	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-121"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-119">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-120'
	>
	Lazy loading in the browsers</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-124"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-122">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-123'
	>
	The specification for web browser native support of lazy-loading landed in the <a href="https://html.spec.whatwg.org/#lazy-loading-attributes">HTML spec</a> a couple of months ago. Now we can write something like:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-126"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-html github-light" data-language="html" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6a737d;">&lt;!--</span><span class="token" style="color: #6a737d;"> lazy load by providing &quot;lazy&quot; value to &quot;loading&quot; attribute</span><span class="token" style="color: #6a737d;">--&gt;</span><span class="token">
</span></span><span class="line"><span class="token">&lt;</span><span class="token" style="color: #22863a;">img</span><span class="token"> </span><span class="token" style="color: #6f42c1;">src</span><span class="token">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">https://infinum.com/uploads/2022/03/lazy-loading-1.html</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token" style="color: #6f42c1;">loading</span><span class="token">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">lazy</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token" style="color: #6f42c1;">width</span><span class="token">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">200</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token" style="color: #6f42c1;">height</span><span class="token">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">200</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token">/&gt;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-129"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-127">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-128'
	>
	Looking at the current specification, it is vague on exactly when browsers are supposed to load a deferred resource. This ambiguity in the specification has created implementations with different user experiences. At the time of writing, Chromium Blink (<strong>Chrome</strong>), Mozilla Gecko (<strong>Firefox</strong>), and WebKit (Safari) have all implemented lazy-loading, and each implementation has set different margins:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-132"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--black block-bullet__bullet" data-id="es-130">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-131'
	>
	<a href="https://addyosmani.com/blog/better-image-lazy-loading-in-chrome/">Blink</a> sets the margin of <strong>1250px</strong> on low-latency network connections, and up to <strong>2500px</strong> on high-latency connections.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-135"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--black block-bullet__bullet" data-id="es-133">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-134'
	>
	<a href="https://hg.mozilla.org/mozilla-central/rev/e27886fc6a2a#l6.40">Gecko</a> sets no margin at all resulting in images being loaded when at least <strong>1px</strong> of it is visible to the user.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-138"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--black block-bullet__bullet" data-id="es-136">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-137'
	>
	<a href="https://bug-196698-attachments.webkit.org/attachment.cgi?id=376259">WebKit</a>’s implementation, although incomplete at the time of writing, seems to set its margins to <strong>100px vertical</strong> and <strong>0px horizontal</strong>. This gives the browser a small heads-up to start loading the image before it’s scrolled into view.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-141"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-139">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-140'
	>
	The browser vendors have gone for different trade-offs between data-saving, perceived performance, and how acceptable a temporary blank area is. These margins aren’t set in stone and may change over time.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-144"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-142">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-143'
	>
	But the inconsistencies don’t end here:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-147"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--black block-bullet__bullet" data-id="es-145">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-146'
	>
	the <code>auto</code> value is not mentioned in the specification but is available (and is the default value) only in Chromium,</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-150"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--black block-bullet__bullet" data-id="es-148">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-149'
	>
	the <code>lazy</code> value with iframes just <a href="https://web.dev/iframe-lazy-loading/">recently</a> got in the standard, and it’s available only in Chromium,</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-153"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--black block-bullet__bullet" data-id="es-151">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-152'
	>
	the <code>loading</code> property is currently <a href="https://caniuse.com/#feat=loading-lazy-attr">supported</a> only by 64% percent of the browsers.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-156"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-154">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-155'
	>
	This is something the web community should stand against, applications whose end-user experiences depend on the browser of choice.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-159"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-157">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-158'
	>
	Even worse, web developers don’t have a say in the matter.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-162"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-160">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-161'
	>
	To get more control over the inconsistencies listed above, let’s try to implement lazy loading on our own.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-165"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-163">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-164'
	>
	The naive approach</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-168"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-166">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-167'
	>
	Only images are used in the following examples, but these approaches can be reused on any other media resource or DOM element. Examples also imply that the user scrolls top-to-bottom, and images get loaded as soon as they enter the viewport.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-171"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-169">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-170'
	>
	With minor code modification, it is possible to create examples that may traverse the page in any direction, and a positive or negative margin on image loading can be added.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-174"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-172">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-173'
	>
	Let’s imagine we have a couple of images in an HTML document. Rather than defining a <code>src</code> attribute which will cause the browser to load the image immediately, let’s set a custom <a href="https://css-tricks.com/a-complete-guide-to-data-attributes/">data attribute</a> named <code>data-lazy</code>:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-176"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-html github-light" data-language="html" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6a737d;">&lt;!--</span><span class="token" style="color: #6a737d;"> regular image </span><span class="token" style="color: #6a737d;">--&gt;</span><span class="token">
</span></span><span class="line"><span class="token">&lt;</span><span class="token" style="color: #22863a;">img</span><span class="token"> </span><span class="token" style="color: #6f42c1;">src</span><span class="token">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">https://infinum.com/uploads/2022/03/lazy-loading-2.html</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token">/&gt;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #6a737d;">&lt;!--</span><span class="token" style="color: #6a737d;"> lazy image </span><span class="token" style="color: #6a737d;">--&gt;</span><span class="token">
</span></span><span class="line"><span class="token">&lt;</span><span class="token" style="color: #22863a;">img</span><span class="token"> </span><span class="token" style="color: #6f42c1;">data-lazy</span><span class="token">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">https://infinum.com/uploads/2022/03/lazy-loading-2.html</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token">/&gt;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-179"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-177">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-178'
	>
	We will read the <code>data-lazy</code> attribute when the image becomes visible in the viewport and use JavaScript to move it to the <code>src</code> attribute. Since we need to know about and react to the visibility of images on the page, let’s listen to the <code>scroll</code> event:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-181"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">lazyImages</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #24292e;">document</span><span class="token">.</span><span class="token" style="color: #6f42c1;">querySelectorAll</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">[data-lazy]</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #24292e;">window</span><span class="token">.</span><span class="token" style="color: #6f42c1;">addEventListener</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">scroll</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #e36209;">event</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #24292e;">lazyImages</span><span class="token">.</span><span class="token" style="color: #6f42c1;">forEach</span><span class="token">(</span><span class="token" style="color: #e36209;">img</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">top</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #6f42c1;">getBoundingClientRect</span><span class="token">(</span><span class="token">)</span><span class="token">.</span><span class="token" style="color: #24292e;">top</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">if</span><span class="token"> </span><span class="token">(</span><span class="token" style="color: #24292e;">top</span><span class="token"> </span><span class="token" style="color: #d73a49;">&lt;=</span><span class="token"> </span><span class="token" style="color: #24292e;">window</span><span class="token">.</span><span class="token" style="color: #24292e;">innerHeight</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #6f42c1;">setAttribute</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">src</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #24292e;">dataset</span><span class="token">.</span><span class="token" style="color: #24292e;">lazy</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-184"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-182">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-183'
	>
	First, we select all the images to be lazy-loaded using an <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors">attribute selector</a>. After that, we set up a callback that will be executed whenever the <code>scroll</code> event fires.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-187"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-185">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-186'
	>
	Each time the scroll event fires, we iterate through the collection of images and check if any entered the viewport. Any image entering the viewport will get its <code>src</code> attribute set, which will trigger the browser to download and present the image to the user.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-190"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-188">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-189'
	>
	Note that both <code>resize</code> and <code>orientationChange</code> events are equally important since resizing the browser window or changing the orientation of a device might make an image enter the viewport. They are omitted for the sake of simplicity of the example.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-193"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-191">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-192'
	>
	The above code snippet has quite a few problems, the first one being efficiency. Scrolling fires off many events, and the browser will need to recalculate every element in the DOM each time. The second issue is iOS. <a href="https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html">Scrolling on some phones</a> only results in the <code>scroll</code> event being fired after the scrolling has finished.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-196"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-194">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-195'
	>
	Thankfully, <a href="https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame"><code>requestAnimationFrame</code></a> (rAF) can help us with these issues:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-198"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">lazyImages</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #24292e;">document</span><span class="token">.</span><span class="token" style="color: #6f42c1;">querySelectorAll</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">[data-lazy]</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">function</span><span class="token"> </span><span class="token" style="color: #6f42c1;">loop</span><span class="token">(</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> The usual lazy loading business</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #24292e;">lazyImages</span><span class="token">.</span><span class="token" style="color: #6f42c1;">forEach</span><span class="token">(</span><span class="token" style="color: #e36209;">img</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">top</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #6f42c1;">getBoundingClientRect</span><span class="token">(</span><span class="token">)</span><span class="token">.</span><span class="token" style="color: #24292e;">top</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #d73a49;">if</span><span class="token"> </span><span class="token">(</span><span class="token" style="color: #24292e;">top</span><span class="token"> </span><span class="token" style="color: #d73a49;">&lt;=</span><span class="token"> </span><span class="token" style="color: #24292e;">window</span><span class="token">.</span><span class="token" style="color: #24292e;">innerHeight</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #6f42c1;">setAttribute</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">src</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #24292e;">dataset</span><span class="token">.</span><span class="token" style="color: #24292e;">lazy</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> This is where the magic happens!</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6f42c1;">requestAnimationFrame</span><span class="token">(</span><span class="token" style="color: #24292e;">loop</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #6f42c1;">loop</span><span class="token">(</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-201"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-199">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-200'
	>
	rAF tells the browser to execute the provided callback before the next screen repaint. By having the initial <code>loop</code> method call, we can witness a beautiful <strong>infinite recursion</strong> of ’self-scheduling’ rAFs. A recursively scheduled callback can be executed from 0 to X times per second, where X is the display refresh rate (depending on the amount of work the callback does) but will generally match the display refresh rate in most web browsers as per W3C recommendation.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-204"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-202">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-203'
	>
	Most devices have a display with 60Hz refresh rate, so rAF will usually fire 60 times a second (if the amount of work in the callback is small enough). Remember kids: To iterate is human, but to recurse is divine.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-207"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-205">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-206'
	>
	This approach is more convenient than listening to the <code>scroll</code> fired hundreds or even thousands of times during scrolling. One further optimization we could do is to remove the image from the collection of lazy images once it gets loaded. However, this still doesn’t scale well. Now, we have an infinite loop performing checks on images even though the user might not even scroll.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-210"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-208">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-209'
	>
	Also, calling <code>getBoundingClientRect</code> will trigger the browser to calculate the style and layout synchronously. This is also called <a href="https://gist.github.com/paulirish/5d52fb081b3570c81e3a">reflow or layout thrashing</a> and is a common performance bottleneck. A large number of DOM elements reflowing many times a second is going to cause jank and ruin the precious user experience, especially for users browsing the web using low-end devices.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-213"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-211">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-212'
	>
	If only there was a way to offload all this work to the browser…</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-216"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-214">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-215'
	>
	The sophisticated approach</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-219"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-217">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-218'
	>
	Meet <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">Intersection Observer API</a>: a way to asynchronously observe changes in the intersection of a <strong>target</strong> element with an ancestor element or with a top-level document’s viewport. <strong>Asynchronously</strong> is the keyword here: We let the browser do the heavy-lifting and let it notify us when the intersection happens.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-222"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-220">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-221'
	>
	At the core of the Intersection Observer API are the following two lines:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-224"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">observer</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">new</span><span class="token"> </span><span class="token" style="color: #6f42c1;">IntersectionObserver</span><span class="token">(</span><span class="token" style="color: #24292e;">callback</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #24292e;">options</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #24292e;">observer</span><span class="token">.</span><span class="token" style="color: #6f42c1;">observe</span><span class="token">(</span><span class="token" style="color: #24292e;">target</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-227"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-225">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-226'
	>
	By providing an <code>options</code> object, we can define:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-231"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--number bullet__color--black block-bullet__bullet" data-id="es-228">
	<p	class='typography typography--size-14-text js-typography bullet__dot'
	data-id='es-229'
	>
	1</p>	<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-230'
	>
	The area to observe with the root property,</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-235"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--number bullet__color--black block-bullet__bullet" data-id="es-232">
	<p	class='typography typography--size-14-text js-typography bullet__dot'
	data-id='es-233'
	>
	2</p>	<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-234'
	>
	How much to shrink <span style="color: var(--typography-color);font-family: var(--typography-font-family);font-size: var(--typography-font-size);font-weight: var(--typography-font-weight);letter-spacing: var(--typography-letter-spacing);text-transform: var(--typography-uppercase,normal)">or expand the root’s logical size when calculating intersections with the </span><code>rootMargin</code><span style="color: var(--typography-color);font-family: var(--typography-font-family);font-size: var(--typography-font-size);font-weight: var(--typography-font-weight);letter-spacing: var(--typography-letter-spacing);text-transform: var(--typography-uppercase,normal)"> property,</span></p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-239"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--number bullet__color--black block-bullet__bullet" data-id="es-236">
	<p	class='typography typography--size-14-text js-typography bullet__dot'
	data-id='es-237'
	>
	3</p>	<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-238'
	>
	Breakpoints for invoking the handler with the <code>threshold</code> property.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-242"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-240">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-241'
	>
	The default threshold is 0, which invokes the handler whenever a target becomes partially visible or completely invisible. Setting the threshold to 1 would fire the handler whenever the target flips between fully visible and partially visible and setting it to 0.5 would fire when the target passes the point of 50% visibility (in either direction). It is also possible to define an array of thresholds to invoke the callback on multiple breakpoints.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-245"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-243">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-244'
	>
	Observers default to monitoring the browser’s viewport if the <code>options</code> object is omitted or the ’root’ property is not provided (or is <code>null</code>).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-248"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-246">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-247'
	>
	Observer handlers are callbacks that receive two arguments:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-252"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--number bullet__color--black block-bullet__bullet" data-id="es-249">
	<p	class='typography typography--size-14-text js-typography bullet__dot'
	data-id='es-250'
	>
	1</p>	<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-251'
	>
	A list of <a style="font-family: var(--typography-font-family);font-size: var(--typography-font-size);font-weight: var(--typography-font-weight);letter-spacing: var(--typography-letter-spacing);text-transform: var(--typography-uppercase,normal)" href="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry"><code>IntersectionObserverEntry</code></a><span style="color: var(--typography-color);font-family: var(--typography-font-family);font-size: var(--typography-font-size);font-weight: var(--typography-font-weight);letter-spacing: var(--typography-letter-spacing);text-transform: var(--typography-uppercase,normal)"> objects, each containing metadata about how a target’s intersection has changed since the last invocation of the handler.</span></p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-256"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--number bullet__color--black block-bullet__bullet" data-id="es-253">
	<p	class='typography typography--size-14-text js-typography bullet__dot'
	data-id='es-254'
	>
	2</p>	<div class="bullet__content">
		<p	class='typography typography--size-16-text-roman js-typography bullet__paragraph'
	data-id='es-255'
	>
	A reference to the observer itself.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-259"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-257">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-258'
	>
	It looks something like the following:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-261"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">function</span><span class="token"> </span><span class="token" style="color: #6f42c1;">callback</span><span class="token"> </span><span class="token">(</span><span class="token" style="color: #e36209;">entries</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #e36209;">observer</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6f42c1;">entries</span><span class="token">.</span><span class="token" style="color: #6f42c1;">forEach</span><span class="token">(</span><span class="token" style="color: #e36209;">entry</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token" style="color: #d73a49;">&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #6f42c1;">if</span><span class="token"> </span><span class="token">(</span><span class="token">entry.isIntersecting</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Do something</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-264"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-262">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-263'
	>
	A single target can have multiple <code>entries</code>, each corresponding to a single threshold. It’s often necessary to check which threshold triggered the <code>callback</code> by iterating over the <code>entries</code>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-267"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-265">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-266'
	>
	The following code snippet represents our previous image lazy loading idea rewritten with the Intersection Observer:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-269"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">lazyImages</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #24292e;">document</span><span class="token">.</span><span class="token" style="color: #6f42c1;">querySelectorAll</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">[data-lazy]</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #24292e;">lazyImages</span><span class="token">.</span><span class="token" style="color: #6f42c1;">forEach</span><span class="token">(</span><span class="token" style="color: #e36209;">img</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Callback which will get invoked once the image enters the viewport</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #6f42c1;">callback</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">(</span><span class="token" style="color: #e36209;">entries</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #e36209;">observer</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> With no options provided, threshold defaults to 0 which results</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> in an array of entries storing only ONE element</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token" style="color: #24292e;">entries</span><span class="token">.</span><span class="token" style="color: #6f42c1;">forEach</span><span class="token">(</span><span class="token" style="color: #e36209;">entry</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token" style="color: #d73a49;">if</span><span class="token"> </span><span class="token">(</span><span class="token" style="color: #24292e;">entry</span><span class="token">.</span><span class="token" style="color: #24292e;">isIntersecting</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">img</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #24292e;">entry</span><span class="token">.</span><span class="token" style="color: #24292e;">img</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #6f42c1;">setAttribute</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">src</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #24292e;">img</span><span class="token">.</span><span class="token" style="color: #24292e;">dataset</span><span class="token">.</span><span class="token" style="color: #24292e;">lazy</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Observer can be disconnected to further optimize efficiency and not</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> to trigger the callback when the image exits the viewport</span><span class="token">
</span></span><span class="line"><span class="token">       </span><span class="token" style="color: #24292e;">observer</span><span class="token">.</span><span class="token" style="color: #6f42c1;">disconnect</span><span class="token">(</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">     </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">   </span><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token">}</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Create an observer for each image</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">io</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">new</span><span class="token"> </span><span class="token" style="color: #6f42c1;">IntersectionObserver</span><span class="token">(</span><span class="token" style="color: #24292e;">callback</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #24292e;">io</span><span class="token">.</span><span class="token" style="color: #6f42c1;">observe</span><span class="token">(</span><span class="token" style="color: #24292e;">img</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-272"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-270">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-271'
	>
	There is no need to perform any calculations (looking at you <code>getBoundingClientRect</code>) because the <code>isIntersecting</code> value stores the information whether or not the image is visible. Once the image is visible, we can disconnect the observer so that the callback doesn’t get triggered as soon as the image leaves the viewport.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-275"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-273">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-274'
	>
	It is also possible to create a single Intersection Observer that observes multiple targets in order to save more resources. If <code>n</code> images are observed, <strong>at most <code>2n</code> callbacks will be executed</strong>: once on page load (to check if the target is already in the viewport) and once the target enters the viewport. That is just crazy performant compared to the naive methods!</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-278"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-276">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-277'
	>
	For a better understanding, check out below the visualization demo created by&nbsp;<a href="https://michellebarker.co.uk/" target="_blank" rel="noreferrer noopener">Michelle Barker</a>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-281"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-279"
	 data-media-type='embed'>

	<div class="embed block-media__embed" data-id=es-280>
	<iframe
		class="embed__iframe block-media__embed-iframe"
		src="https://codepen.io/michellebarker/pen/xxwLpRG"
		frameborder="0"
		aria-label="Embed iframe"
		allow="autoplay; accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture;"
		allowfullscreen>
	</iframe>
</div></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-284"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-282">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-283'
	>
	All the major browsers have supported it for some time now. As expected, Internet Explorer doesn’t support it at any level, but there’s a <a href="https://github.com/w3c/IntersectionObserver">polyfill</a> available from the W3C that takes care of that.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-287"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-285">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-286'
	>
	And yes, all the browsers that implemented native lazy loading used Intersection Observer under the hood.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-290"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-288">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-289'
	>
	Creating better user experiences with lazy loading</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-293"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-291">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-292'
	>
	Avoiding Content Reflow</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-296"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-294">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-295'
	>
	When an image is yet to be downloaded, the browser doesn’t know the size it will take up. Then the enclosing container would have no dimensions if we do not specify it using CSS.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-299"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-297">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-298'
	>
	Once the image loads, the browser will drop it onto the screen. This sudden change in the layout causes other elements to move around and causes <strong>reflow</strong>. That isn&#8217;t just an unpleasant user experience but also a potential performance problem.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-302"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-300"
	 data-media-type='video'>

	<div class="video__wrapper" data-id="es-301">
		<video
		class="video block-media__video js-video js-block-media-video video--cursor-takeover-use"
		 loop autoplay playsinline muted preload='metadata'>
		<source  src='https://infinum.com/uploads/2022/04/reflow-1.mp4' type='video/mp4' />	</video>
	</div></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-305"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-303">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-304'
	>
	This can be avoided by <a href="https://www.smashingmagazine.com/2020/03/setting-height-width-images-important-again/">specifying the height and/or width of the enclosing container</a>. Later, when the image loads, since the container size is already specified and the image fits into that perfectly, the rest of the content around that container does not move.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-308"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-306"
	 data-media-type='video'>

	<div class="video__wrapper" data-id="es-307">
		<video
		class="video block-media__video js-video js-block-media-video video--cursor-takeover-use"
		 loop autoplay playsinline muted preload='metadata'>
		<source  src='https://infinum.com/uploads/2022/04/reflow-fix.mp4' type='video/mp4' />	</video>
	</div></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-311"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-309">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-310'
	>
	Utilizing Placeholders</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-314"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-312">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-313'
	>
	What if there is no way to know the dimensions of an image? Or if an image download takes a lot of time because of its size? An alternative could be to use a placeholder. A placeholder is an element that appears in the container until the actual image is loaded. One could pick a <strong>Generic Placeholder</strong> (low-quality image with a fixed color), a <strong>Dominant Color Placeholder</strong> (e.g. Pinterest), or even a Low-Quality<strong> Image Placeholder</strong> (blurred version of the original, e.g. Facebook or Medium; can be seen in the following video).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-317"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-315"
	 data-media-type='embed'>

	<div class="embed block-media__embed" data-id=es-316>
	<iframe
		class="embed__iframe block-media__embed-iframe"
		src="https://www.youtube.com/embed/BXoNamwkqi0"
		frameborder="0"
		aria-label="Embed iframe"
		allow="autoplay; accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture;"
		allowfullscreen>
	</iframe>
</div></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-320"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-318">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-319'
	>
	The switch could be achieved by setting the <code>src</code> attribute to point to a placeholder then switching its value to the original image once it meets the lazy-loading criteria.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-323"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-321">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-322'
	>
	One problem with this approach is that we have to maintain at least two versions of an image. Another problem is that downloading the placeholders is still present on page load, but the data amount is smaller than loading the originals. Also, smaller placeholder images could be inlined as data urls to save up on network requests.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-326"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-324">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-325'
	>
	Nevertheless, it is clear that the transition from the placeholder to the actual image gives the user an idea of what is to come in place of that placeholder and improves loading perception.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-329"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-327">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-328'
	>
	Setting the margins</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-332"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-330">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-331'
	>
	In the previous examples, we checked for the point of time where the image enters the viewport. The problem with this approach is that users might scroll really fast through the page, and the image will need some time to load and appear on the screen.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-335"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-333">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-334'
	>
	Instead of loading the image exactly when it enters the viewport, some margins could be added. This provides additional time, between the load trigger and the actual entry in the viewport, for the images to load. This margin could also be dynamically adjusted depending on the user’s device (using the User-Agent Strings) or even using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API">Network Information API</a>. Smaller margins could be set for users with good network and performant devices since they can download, decode, and render resources faster than users with low-end devices.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-338"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-336">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-337'
	>
	Not everything must be lazy-loaded</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-341"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-339">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-340'
	>
	Like everything in life, it is possible to have too much of a good thing. Lazy loading might reduce the initial page load, but it also might result in a bad user experience if some resources are deferred when they shouldn’t be. <strong>Resources present in the viewport on page load should probably not be lazy-loaded</strong>. Mobile and desktop devices have different screen sizes and a different number of resources will be visible initially, which should be taken into account.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-344"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-342">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-343'
	>
	Also, what is the point of lazy loading if a page is too small, and there is nothing to scroll?</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-347"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-345">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-346'
	>
	The JavaScript dependency</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-350"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-348">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-349'
	>
	This entire idea of lazy loading depends on JavaScript being enabled and available in the user’s browser. Most of the users will likely have JavaScript enabled, but it is always good to plan for cases where it is not.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-353"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-351">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-352'
	>
	An option is to use the <code>&lt;noscript&gt;</code> tag. But that is as good as it gets and is certainly a story on its own. Check <a href="https://css-tricks.com/the-complete-guide-to-lazy-loading-images/">this Stack Overflow thread</a> to get some clarity on this subject.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-356"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-354">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-355'
	>
	Also, as an anti-tracking measure, the native lazy loading does <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"><em>NOT</em> get deferred when JS is enabled</a>. This is just another reason to ditch the native implementation.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-359"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-357">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-358'
	>
	What about SEO?</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-362"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-360">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-361'
	>
	I can already hear someone in the background yelling: &#8220;But what about SEO, Googlebot can’t execute JavaScript… and it won’t scroll your page.&#8221;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-365"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-363">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-364'
	>
	<a href="https://webmasters.googleblog.com/2019/05/the-new-evergreen-googlebot.html">As of May 2019, Google announced</a> that <a href="https://support.google.com/webmasters/answer/182072?hl=en">Googlebot</a> would regularly update its rendering engine to ensure support for the latest web platform features. And with that, support for Intersection Observer landed! Google is not too specific on how its crawler can index the images (since it doesn’t scroll), but <a href="https://support.google.com/webmasters/thread/7584822?hl=en">it seems</a> it manipulates the IntersectionObserver API to populate the content of the page using it.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-368"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-366">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-367'
	>
	Increase loading performance, reduce the overall page size</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-371"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-369">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-370'
	>
	We covered a lot of ground about lazy loading. If implemented well, it can significantly benefit the loading performance while reducing the overall page size and delivery costs, thanks to deferring unnecessary resources upfront.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-374"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-372">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-373'
	>
	The hype around native lazy loading was quite big, but the specification is vague, and each browser vendor implemented it in its own way. This brings inconsistent user experiences and lowers the trust we have in the applications we build. As developers, we should not demand such out-of-the-box features from our platform. It is tough to create a fully-functioning, unified, and consistent solution for such a complex problem.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-377"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-375">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-376'
	>
	Instead, we should demand optimized low-level APIs (like Intersection Observer) so as to elegantly solve complex problems and create scalable solutions (like lazy-loading).</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/lazy-loading/">Make Webpages Load Faster with Lazy Loading</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
		
	</channel>
</rss>