{"id":28737,"date":"2023-05-04T06:00:00","date_gmt":"2023-05-04T13:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=28737"},"modified":"2024-04-10T07:33:35","modified_gmt":"2024-04-10T14:33:35","slug":"automatically-loading-high-quality-images-cloudinary-intersectionobserver","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver","title":{"rendered":"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><p>Web performance is, or should be, one of the top concerns for any developer. This is certainly not a new issue, but it is refreshing to see our community put more and more emphasis on this topic over the past few years. Even better, it\u2019s great to see browsers add additional support to help us in this endeavor \u2014 from devtools to performance tests and even to new APIs that make developers\u2019 jobs easier. One of these APIs is the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Intersection_Observer_API\">IntersectionObserver<\/a>. This gives developers a way to determine when a particular DOM element has become visible in the browser\u2019s viewport. In this article, I\u2019ll give an introduction to the API and then demonstrate a way to use it with Cloudinary\u2019s <a href=\"https:\/\/cloudinary.com\/image-api\">imaging APIs<\/a>.\n\u200b<\/p>\n<h2>The IntersectionObserver API<\/h2>\n<p>\u200b\nMDN defines the IntersectionObserver feature as such:\n\u200b<\/p>\n<blockquote>\nThe Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document&#8217;s viewport.\n<\/blockquote>\n\u200b\n<p>At a high level, what this means is that we can determine when an item, like an image, becomes visible based on either the viewport of the browser (the entire visible page) or within some other element (imagine a container  <code>div<\/code> for example). The API can be fine-tuned to only inform you when an element is 100% visible, or some other percent. You can also add a margin around the root element being observed.\n\u200b\nYou begin by creating an instance of an IntersectionObserver object. This is done by passing in your options as well as a function to be called when an intersection happens.\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> noticeImage = <span class=\"hljs-function\">(<span class=\"hljs-params\">entries,observer<\/span>) =&gt;<\/span> {\n\u200b\n}\n\u200b\n<span class=\"hljs-keyword\">let<\/span> options = {\n  <span class=\"hljs-attr\">rootMargin<\/span>: <span class=\"hljs-string\">'0px'<\/span>,\n  <span class=\"hljs-attr\">threshold<\/span>: <span class=\"hljs-number\">0.3<\/span>\n}\n\u200b\n<span class=\"hljs-keyword\">let<\/span> observer = <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(noticeImage, options);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nIn the above example, I did not specify a root option. This will then default to the browser\u2019s viewport. I don\u2019t want a margin so I\u2019ve set it to 0. And I\u2019ve specified a threshold of 30% for items. In other words, when 30% or more of the item is found, consider it intersected.\n\u200b\nI\u2019ve also specified an empty handler, <code>noticeImage<\/code>, that I\u2019ll fill out in a moment.<\/p>\n<p>Alright, at this point, I\u2019ve defined an intersection and specified how to watch things within, but I\u2019ve not actually told it what I want to observe. This is done by using the <code>observe<\/code> method on the IntersectionObserver object.<\/p>\n<p>Let\u2019s first add some HTML:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"images\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_2.jpg\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/400_1.jpg\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/400_2.jpg\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/450.jpg\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/666.jpg\"<\/span>&gt;<\/span>\n\t<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/700.jpg\"<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nI\u2019ve also got a bit of CSS to help space them out a bit:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css shcb-wrap-lines\"><span class=\"hljs-selector-id\">#images<\/span> <span class=\"hljs-selector-tag\">img<\/span> {\n\t<span class=\"hljs-attribute\">display<\/span>:block;\n\t<span class=\"hljs-attribute\">margin<\/span>: auto;\n\t<span class=\"hljs-attribute\">padding-top<\/span>: <span class=\"hljs-number\">75px<\/span>;\n\t<span class=\"hljs-attribute\">padding-bottom<\/span>: <span class=\"hljs-number\">75px<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nGiven that we\u2019ve got images on our page, let\u2019s grab all of them within that div element:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">document<\/span>.querySelectorAll(<span class=\"hljs-string\">'#images img'<\/span>).forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">i<\/span> =&gt;<\/span> observer.observe(i));\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nNow we\u2019ve got our IntersectionObserver created and a set of elements it should be looking out for, let\u2019s then flesh out our handler:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> noticeImage = <span class=\"hljs-function\">(<span class=\"hljs-params\">entries, observer<\/span>) =&gt;<\/span> {\n\t<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'noticeImage'<\/span>);\n\tentries.forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> {\n\t\t<span class=\"hljs-keyword\">if<\/span>(e.isIntersecting) {\n\t\t\t<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'i noticed '<\/span>, e.target.src);\n\t\t\tobserver.unobserve(e.target);\n\t\t}\n\t});\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nIntersectionObserver handlers are passed an array of items that have become visible (it\u2019s an array since a user could scroll quickly enough for multiple items to become visible) as well as the original IntersectionObserver object. Each entry contains a number of properties you could use if desired. For now, I only check <code>isIntersecting<\/code>, and if true, I log a message out and then stop observing it. The value would be false if the item <em>left<\/em> the IntersectionObserver root area but since I know our demo is going to be doing one operation per item intersected, I only care when the value is true.<\/p>\n<p>To test this, check out the CodePen below:\n\u200b<\/p>\n<p class=\"codepen\" data-height=\"300\" data-default-tab=\"result\" data-slug-hash=\"mdGZxvw\" data-user=\"cfjedimaster\" style=\"height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;\">\n  <span>See the Pen <a href=\"https:\/\/codepen.io\/cfjedimaster\/pen\/mdGZxvw\">\n  Intersection<\/a> by Raymond Camden (<a href=\"https:\/\/codepen.io\/cfjedimaster\">@cfjedimaster<\/a>)\n  on <a href=\"https:\/\/codepen.io\">CodePen<\/a>.<\/span>\n<\/p>\n<script async src=\"https:\/\/cpwebassets.codepen.io\/assets\/embed\/ei.js\"><\/script>\n\u200b\n<p>But you may have more luck viewing the live view of it here with your console open: <a href=\"https:\/\/codepen.io\/cfjedimaster\/live\/mdGZxvw\">https:\/\/codepen.io\/cfjedimaster\/live\/mdGZxvw<\/a>\n\u200b<\/p>\n<h2>Integrating with Cloudinary<\/h2>\n<p>\u200b\nAlright, now to have some fun with this by integrating Cloudinary. For our demo, we\u2019re going to do two things:\n\u200b<\/p>\n<ol>\n<li>First, we will replace the original images with a lower quality, but still decent version. This will reduce the initial load of content and hopefully make for a better user experience.<\/li>\n<li>Then we will use the IntersectionObserver to make note of when one of our images is visible and replace it with a higher-quality version. What\u2019s nice is that if for some reason the user does not have JavaScript enabled, or if something goes wrong in the code (my fault, not Cloudinary\u2019s), then they still get an image, even if lower quality. You could also consider using an experimental API (Chromium only) like <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Navigator\/connection\">Navigator.connection<\/a> and choose <em>not<\/em> to download a higher res image for people on slower networks.\n\u200b\nLet\u2019s start with the first part. My original images were all hosted like any other regular image. Here\u2019s an example:\n\u200b<\/li>\n<\/ol>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\"<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nAnd the result: <img decoding=\"async\" src=\"https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\" alt=\"Cat image, raw\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"200\" height=\"300\"\/><\/p>\n<p>First, we will move it to Cloudinary\u2019s CDNs by using the <a href=\"https:\/\/cloudinary.com\/documentation\/fetch_remote_images\">Fetch<\/a> feature. This simply involves prepending my URL like so:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/raymondcamden\/image\/fetch\/https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\"<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nThe result is exactly the same:\n\u200b<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/raymondcamden\/image\/fetch\/https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\" alt=\"Cat image, via Cloudinary\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"200\" height=\"300\"\/>\n\u200b<\/p>\n<p>It isn\u2019t exactly the same as Cloudinary will use its CDNs to deliver it to end users better and will cache the data as well.<\/p>\n<p>Next, we\u2019ll lower the quality a bit by using the <a href=\"https:\/\/cloudinary.com\/documentation\/image_optimization#how_to_optimize_image_quality\">quality transformation<\/a> feature in Cloudinary. We can add a <code>q_X<\/code> parameter where the value of <code>X<\/code> can be a number from 0 to 100 (there are other options as well). For our test, we will use a 30% quality setting. Here\u2019s that new version:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/raymondcamden\/image\/fetch\/q_30\/https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\"<\/span>&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nAnd the result:\n\u200b\n<img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/raymondcamden\/image\/fetch\/q_30\/https:\/\/static.raymondcamden.com\/images\/2023\/04\/300_1.jpg\" alt=\"Cat image, via Cloudinary, lower quality\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"200\" height=\"300\"\/>\n\u200b<\/p>\n<p>I can definitely tell a difference, but it\u2019s certainly not bad and if for some reason the higher-quality version didn\u2019t load, I think it would be fine. But let\u2019s go ahead and add that support now. First, let\u2019s change our handler name:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">let<\/span> observer = <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(replaceImage, options);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nAnd now let\u2019s define <code>replaceImage<\/code>:\n\u200b<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">upgrade<\/span>(<span class=\"hljs-params\">u<\/span>) <\/span>{\n\t<span class=\"hljs-keyword\">return<\/span> u.replace(<span class=\"hljs-string\">'q_30'<\/span>,<span class=\"hljs-string\">'q_90'<\/span>);\n}\n\u200b\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">replaceImage<\/span>(<span class=\"hljs-params\">entries, observer<\/span>) <\/span>{\n\t<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'replaceImage'<\/span>);\n\tentries.forEach(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> {\n\t\t<span class=\"hljs-keyword\">if<\/span>(e.isIntersecting) {\n\t\t\t<span class=\"hljs-keyword\">let<\/span> newSrc = upgrade(e.target.src);\n\t\t\t<span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">'upgraded '<\/span>,e.target.src);\n\t\t\te.target.src = newSrc;\n\t\t\tobserver.unobserve(e.target);\n\t\t}\n\t});\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>\u200b\nNow we make use of a utility function, <code>upgrade<\/code>, which handles rewriting the Cloudinary URL to change our quality from 30 to 90%. We could load the original of course, but some lowering of quality is probably still preferable here. Also, note that Cloudinary has a JavaScript SDK to generate these URLs for you, but in our case, it was a very simple string replacement. You can play with this version here:\n\u200b<\/p>\n<p class=\"codepen\" data-height=\"300\" data-default-tab=\"result\" data-slug-hash=\"abjXgye\" data-user=\"cfjedimaster\" style=\"height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;\">\n  <span>See the Pen <a href=\"https:\/\/codepen.io\/cfjedimaster\/pen\/abjXgye\">\n  Cloudinary + Intersection<\/a> by Raymond Camden (<a href=\"https:\/\/codepen.io\/cfjedimaster\">@cfjedimaster<\/a>)\n  on <a href=\"https:\/\/codepen.io\">CodePen<\/a>.<\/span>\n<\/p>\n<script async src=\"https:\/\/cpwebassets.codepen.io\/assets\/embed\/ei.js\"><\/script>\n<\/div>\n\n<div class=\"wp-block-cloudinary-markdown \"><p>And as before, if you want to observe the console messages, open it in the live view <a href=\"https:\/\/codepen.io\/cfjedimaster\/live\/abjXgye\">here<\/a>.\n\u200b<\/p>\n<h2>Last thoughts<\/h2>\n<p>\u200b\nAs I wrap up, here are a few things to consider.\n\u200b<\/p>\n<ul>\n<li>The IntersectionObserver is a cool API, but note you can also use <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Performance\/Lazy_loading\">lazy loading<\/a> via a simple HTML attribute. This would be simpler, but the JavaScript solution does give you more fine grained control. All I know is that as a web developer, I love having options!<\/li>\n<li>While not particularly important to the point of this article, I\u2019ll note that my images were all sourced from the wonder <a href=\"https:\/\/placekitten.com\">Placekitten<\/a> service. I originally used their URLs, but discovered in testing that they will return random images of the same size. I wanted consistency in my results so I downloaded copies.<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":28742,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[332,370,227],"class_list":["post-28737","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-api","tag-image","tag-performance-optimization"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-05-04T13:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-04-10T14:33:35+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver-jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver\",\"datePublished\":\"2023-05-04T13:00:00+00:00\",\"dateModified\":\"2024-04-10T14:33:35+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\"},\"wordCount\":8,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA\",\"keywords\":[\"API\",\"Image\",\"Performance Optimization\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2023\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\",\"url\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\",\"name\":\"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA\",\"datePublished\":\"2023-05-04T13:00:00+00:00\",\"dateModified\":\"2024-04-10T14:33:35+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver","og_locale":"en_US","og_type":"article","og_title":"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver","og_url":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver","og_site_name":"Cloudinary Blog","article_published_time":"2023-05-04T13:00:00+00:00","article_modified_time":"2024-04-10T14:33:35+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver-jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver","datePublished":"2023-05-04T13:00:00+00:00","dateModified":"2024-04-10T14:33:35+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver"},"wordCount":8,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA","keywords":["API","Image","Performance Optimization"],"inLanguage":"en-US","copyrightYear":"2023","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver","url":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver","name":"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA","datePublished":"2023-05-04T13:00:00+00:00","dateModified":"2024-04-10T14:33:35+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/automatically-loading-high-quality-images-cloudinary-intersectionobserver#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Automatically Loading High-Quality Images with Cloudinary and IntersectionObserver"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1683047578\/blog-Cloudinary-and-IntersectionObserver\/blog-Cloudinary-and-IntersectionObserver.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28737","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28737"}],"version-history":[{"count":22,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28737\/revisions"}],"predecessor-version":[{"id":39410,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28737\/revisions\/39410"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28742"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28737"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28737"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28737"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}