{"id":38019,"date":"2025-07-23T07:00:00","date_gmt":"2025-07-23T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=38019"},"modified":"2025-10-31T12:57:54","modified_gmt":"2025-10-31T19:57:54","slug":"lazy-load-autoplay-videos-core-web-vitals","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals","title":{"rendered":"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals"},"content":{"rendered":"\n<p>Autoplay videos can work exceptionally well in specific contexts. Want a muted promotional banner to showcase your products? An interactive product tour? A hero section that shares your brand at a glance? When set up correctly, autoplay videos increase engagement, as users can watch your content without the extra step of clicking. The challenge is making sure your video is optimized so it doesn\u2019t tank the user\u2019s experience.<\/p>\n\n\n\n<p>This guide will walk you through how &nbsp;to deliver smooth autoplay videos that lazy load and play fast. <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/documentation\/video_optimization\" rel=\"noreferrer noopener\">Cloudinary&#8217;s intelligent video optimization<\/a> can automatically handle format selection to quality adjustment, while you focus on creating amazing user experiences.<\/p>\n\n\n\n<p>To get started, it\u2019s helpful to understand the foundational problem with videos and Core Web Vitals, then dive into the practical solutions. Make sure to complete the prerequisites below to follow along.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<p>You should have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Basic knowledge of HTML, CSS, and JavaScript.<\/li>\n\n\n\n<li>Understanding of web performance concepts.<\/li>\n\n\n\n<li>A free <a href=\"https:\/\/cloudinary.com\/users\/register\/free\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary account<\/a> for video optimization.<\/li>\n\n\n\n<li>A local development server (Python, Node.js, or VS Code Live Server).<\/li>\n<\/ul>\n\n\n\n<p>You can also view the complete <a target=\"_blank\" href=\"https:\/\/github.com\/Olanetsoft\/video-optimisation-tutorial\" rel=\"noreferrer noopener\">source code on GitHub<\/a> to see the end result.<\/p>\n\n\n\n<p>Videos impacts the critical metrics Google uses to evaluate your site&#8217;s performance, which is known as the Core Web Vitals triad. Let\u2019s break down how and why:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/web.dev\/lcp\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Largest Contentful Paint<\/strong><\/a><strong> (LCP).<\/strong> This measures loading performance, i.e., how long the most significant element in the visible viewport takes to load. Videos that serve as hero elements are often the LCP element, and slow-loading videos directly increase this metric.<\/li>\n\n\n\n<li><a href=\"https:\/\/web.dev\/cls\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>First Input Delay<\/strong><\/a><strong> (FID)<\/strong>. This measures interactivity, as heavy video processing can block the main thread, increasing input delay.&nbsp;<\/li>\n\n\n\n<li><a href=\"https:\/\/web.dev\/inp\/\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Cumulative Layout Shift<\/strong><\/a><strong> (CLS)<\/strong>. This measures visual stability. Videos without reserved dimensions cause significant layout shifts as they load. To provide a good user experience, pages should maintain a cumulative layout shift (CLS) of less than 0.1 layout shift score.<\/li>\n<\/ul>\n\n\n\n<p>With critical impact metrics Google uses to evaluate site performance, poorly optimized videos can compromise all three metrics, reducing your overall page performance score and negatively impacting SEO rankings and user experience.<\/p>\n\n\n\n<p>Next, let&#8217;s tackle the problem. The key to maintaining fast performance while using engaging video content is <strong>lazy loading<\/strong>. In other words, ensuring videos only load when users need to see them. This prevents unnecessary bandwidth consumption and keeps your initial page load lightning-fast.<\/p>\n\n\n\n<p>In the next section, you&#8217;ll implement three progressively lazy loading methods, starting with simple native HTML attributes and advancing to intelligent, connection-aware optimization with Cloudinary.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Set Up the Project and Install Dependencies<\/h2>\n\n\n\n<p>First, create the project using the following command to build a hands-on demo page to test each method side by side, with live performance feedback so you can see the differences in action using vanilla HTML, CSS, and JavaScript.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Create project directory<\/span>\n\nmkdir video-optimization-tutorial\n\ncd video-optimization-tutorial<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Next, create the basic file structure:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Create the basic file structure<\/span>\n\nmkdir css js images videos\n\ntouch index.html css\/styles.css js\/main.js js\/cloudinary-config.js<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then start or set up your local server with the following command:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Using Live Server extension in VS Code (recommended)<\/span>\n\n<span class=\"hljs-comment\"># Just right-click index.html and select \"Open with Live Server\"<\/span>\n\n<span class=\"hljs-comment\"># Option 2: Using Node.js http-server<\/span>\n\nnpm install -g http-server\n\nhttp-server<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Your project structure should look like this:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">video-optimization-tutorial\/\n\n\u251c\u2500\u2500 index.html\n\n\u251c\u2500\u2500 css\/\n\n\u2502 \u00a0 \u2514\u2500\u2500 styles.css\n\n\u251c\u2500\u2500 js\/\n\n\u2502 \u00a0 \u251c\u2500\u2500 main.js\n\n\u2502 \u00a0 \u2514\u2500\u2500 cloudinary-config.js\n\n\u251c\u2500\u2500 images\/\n\n\u2514\u2500\u2500 videos\/<\/code><\/span><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Get Environment Variables From Cloudinary<\/h2>\n\n\n\n<p>You need environment credentials from your Cloudinary dashboard to use Cloudinary video transformations and create optimized video delivery.<\/p>\n\n\n\n<p>Log in to your <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/\" rel=\"noreferrer noopener\">Cloudinary dashboard<\/a> to retrieve environment credentials such as the <strong>cloud name<\/strong>, <strong>API key<\/strong>, and <strong>API secret<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1753214121\/blog-How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Your_Core_Web_Vitals-1.png\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Get product environment credentials on Cloudinary<\/figcaption><\/figure>\n\n\n\n<p>Add your environment credentials inside the <code>js\/cloudinary-config.js<\/code> file:<\/p>\n\n\n<pre class=\"wp-block-code\" 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-comment\">\/\/ js\/cloudinary-config.js<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> CLOUDINARY_CONFIG = {\n\n\u00a0 <span class=\"hljs-attr\">cloudName<\/span>: <span class=\"hljs-string\">'your-cloud-name'<\/span>, <span class=\"hljs-comment\">\/\/ Replace with your actual cloud name<\/span>\n\n\u00a0 <span class=\"hljs-attr\">apiKey<\/span>: <span class=\"hljs-string\">'your-api-key'<\/span> <span class=\"hljs-comment\">\/\/ Only needed for uploads, not transformations<\/span>\n\n};\n\n<span class=\"hljs-comment\">\/\/ Initialize Cloudinary (we'll add this script tag to HTML)<\/span>\n\n<span class=\"hljs-keyword\">const<\/span> cloudinary = <span class=\"hljs-built_in\">window<\/span>.cloudinary ? <span class=\"hljs-built_in\">window<\/span>.cloudinary.Cloudinary.new({\n\n\u00a0 <span class=\"hljs-attr\">cloud_name<\/span>: CLOUDINARY_CONFIG.cloudName,\n\n\u00a0 <span class=\"hljs-attr\">secure<\/span>: <span class=\"hljs-literal\">true<\/span>\n\n}) : <span class=\"hljs-literal\">null<\/span>;<\/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\n\n<p>Replace <code>your-cloud-name<\/code> with your actual Cloudinary cloud name.<\/p>\n\n\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>For client-side implementations, you only need the cloud name. API keys and secrets are only required for server-side uploads and sensitive operations.<\/p>\n<\/div>\n\n\n<p>Add any video of your choice to the videos folder. For experimentation, you can use these examples: <a target=\"_blank\" href=\"https:\/\/asset.cloudinary.com\/dzajbtuk2\/17a1b5c44beea417c89b2c40f0c6fd0c\" rel=\"noreferrer noopener\">demo-video<\/a> and <a target=\"_blank\" href=\"https:\/\/asset.cloudinary.com\/dzajbtuk2\/8818df5309234a9e9acf915fe47c63aa\" rel=\"noreferrer noopener\">hero-video<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Implement Lazy Loading for Videos<\/h2>\n\n\n\n<p>Lazy loading ensures videos don&#8217;t load until needed, saving bandwidth and improving initial page load times. There are three main approaches you can use:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/docs.optimole.com\/article\/1949-browser-native-lazy-load\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Native Lazy Loading<\/strong><\/a><strong>.<\/strong> Using HTML5&#8217;s built-in <code>loading=\"lazy\"<\/code> attribute.<\/li>\n\n\n\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Intersection_Observer_API\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Intersection Observer API<\/strong><\/a><strong>.<\/strong> JavaScript-based approach for advanced control.<\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/nextjs_lazy_loading_tutorial\" target=\"_blank\" rel=\"noreferrer noopener\"><strong>Cloudinary SDK Lazy Loading<\/strong><\/a><strong>.<\/strong> Automated optimization with intelligent loading.<\/li>\n<\/ul>\n\n\n\n<p>Let&#8217;s implement each approach step by step.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Method 1: Native Lazy Loading<\/strong><\/h3>\n\n\n\n<p>Native lazy loading is the most straightforward approach, but it has some limitations. Let&#8217;s implement it by creating the base HTML structure.<\/p>\n\n\n\n<p>To create the base HTML structure, add <a href=\"https:\/\/gist.github.com\/Olanetsoft\/ec8927f80f3a8331c12fa8772f710dbd\" target=\"_blank\" rel=\"noreferrer noopener\">this code snippet<\/a> to your <code>index.html<\/code>. Then add <a href=\"https:\/\/gist.github.com\/Olanetsoft\/358b924517b4780e93d30b608aefbc5c\" target=\"_blank\" rel=\"noreferrer noopener\">this CSS styles<\/a> to your css\/styles.css.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Test the Lazy Load Implementation<\/strong><\/h4>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Open Chrome DevTools (F12) and go to the Network tab.<\/li>\n\n\n\n<li>Refresh the page and don&#8217;t scroll yet.<\/li>\n\n\n\n<li>Check the <strong>Network<\/strong> tab. You should only see the hero-demo video loading.<\/li>\n\n\n\n<li>Scroll down towards the demo section.<\/li>\n\n\n\n<li>Watch the <strong>Network<\/strong> tab. The lazy video should load when you get close (~1000px away).<\/li>\n\n\n\n<li>Notice the timing. The <code>demo-video<\/code> loads automatically before you can see it.<\/li>\n<\/ol>\n\n\n\n<p>You should observe that the second video only loads when you scroll near it, saving bandwidth on initial page load.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1753214131\/blog-How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Your_Core_Web_Vitals-2.gif\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Method 1: Native Lazy Loading<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Method 2: Intersection Observer API<\/strong><\/h3>\n\n\n\n<p>While native lazy loading is simple, it offers limited control over when the video loads and provides no feedback on the loading process. For more precise control, real-time status updates, and coordination with autoplay, the Intersection Observer API is a powerful JavaScript-based solution.<\/p>\n\n\n\n<p>This method allows you to define exactly when a video should start loading based on its visibility within the viewport or a specified margin around it.<\/p>\n\n\n\n<p>Next, you should modify the index.html to set up the demonstration for the Intersection Observer. This includes a dedicated section for this method, status display elements to provide real-time feedback, and a video element that will be controlled by our JavaScript.<\/p>\n\n\n\n<p>Update the index.html file with <a target=\"_blank\" href=\"https:\/\/gist.github.com\/Olanetsoft\/c09407821aa28bd3e619011c194abd50\" rel=\"noreferrer noopener\">this code snippet<\/a>. Then, add <a target=\"_blank\" href=\"https:\/\/gist.github.com\/Olanetsoft\/41cbecd4c46f79f99cda928897376345\" rel=\"noreferrer noopener\">these specific styles<\/a> inside the css\/styles.css file for the new elements introduced in Method 2, particularly for the status display and its dynamic coloring. Some styles might be similar to Method 1.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Implement JavaScript Logic<\/strong><\/h4>\n\n\n\n<p>This is where the Intersection Observer comes into play. You&#8217;ll write a script to observe the lazy-load video. When it&#8217;s about to enter the viewport (within a <strong>200px<\/strong> margin), your script will dynamically load its source and attempt to autoplay it. It will also update the status display elements to show what&#8217;s happening.<\/p>\n\n\n\n<p>First, you must ensure your script waits for the HTML to be fully loaded before it tries to find elements on the page. You also need a way to check if the user&#8217;s browser even supports the Intersection Observer API.<\/p>\n\n\n\n<p>To do this, add the following code to your js\/main.js file. This sets up an event listener for when the page is ready and includes a helper function to check for Intersection Observer support. If it&#8217;s not supported, the status display will be updated accordingly.<\/p>\n\n\n<pre class=\"wp-block-code\" 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-comment\">\/\/ js\/main.js - Method 2: Intersection Observer API<\/span>\n\n<span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">\"DOMContentLoaded\"<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\ud83c\udfac Method 2: Intersection Observer loaded\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!isIntersectionObserverSupported()) {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"\u26a0\ufe0f Intersection Observer not supported in this browser\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> statusElement = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"observer-status\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (statusElement) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ This relies on updateStatus, which you'll add soon.<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ For now, you could use: statusElement.textContent = \"Browser not supported\";<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ statusElement.className = \"value error\";\u00a0<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ We'll define updateStatus next to handle this more cleanly.<\/span>\n\n\u00a0 \u00a0 }\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> distanceElem = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"observer-distance\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (distanceElem) distanceElem.textContent = <span class=\"hljs-string\">\"-\"<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ The call to updateStatus will be fully functional after you add that function.<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ For now, this structure prepares for it.<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span>;\u00a0\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Initialize the Intersection Observer for lazy loading<\/span>\n\n\u00a0 setupIntersectionObserver(); <span class=\"hljs-comment\">\/\/ You'll define this function next.<\/span>\n\n});\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">isIntersectionObserverSupported<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">\"IntersectionObserver\"<\/span> <span class=\"hljs-keyword\">in<\/span> <span class=\"hljs-built_in\">window<\/span>;\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\n<div class='c-callout  c-callout--inline-title c-callout--note'><strong class='c-callout__title'>Note:<\/strong> <p>The updateStatus call inside the if block will make more sense once you add the updateStatus function in the next step. For now, it sets the stage.<\/p>\n<\/div>\n\n\n<p>To give users clear feedback, you need functions to update the status messages (like &#8220;Loading&#8230;&#8221;, &#8220;Playing&#8221;) and the distance display.&nbsp;<\/p>\n\n\n\n<p>Add the <code>updateStatus<\/code> function to your <code>js\/main.js<\/code>. This helper will change the text and apply a CSS class to the status element on your page, making it easy to see the current state.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateStatus<\/span>(<span class=\"hljs-params\">statusElement, text, className<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!statusElement) <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 statusElement.textContent = text;\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> classesToRemove = &#91;<span class=\"hljs-string\">\"not-loaded\"<\/span>, <span class=\"hljs-string\">\"loading\"<\/span>, <span class=\"hljs-string\">\"loaded\"<\/span>, <span class=\"hljs-string\">\"playing\"<\/span>, <span class=\"hljs-string\">\"error\"<\/span>, <span class=\"hljs-string\">\"paused\"<\/span>];\n\n\u00a0 statusElement.classList.remove(...classesToRemove);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (className) {\n\n\u00a0 \u00a0 statusElement.classList.add(className);\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`\ud83d\udcca Status updated: <span class=\"hljs-subst\">${text}<\/span>`<\/span>);\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><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\n\n<p>Now that <code>updateStatus<\/code> is defined, you can complete the browser support check. Go back to your <code>DOMContentLoaded<\/code> listener and ensure the <code>updateStatus<\/code> call is present if the browser isn&#8217;t supported:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-comment\">\/\/ Inside your DOMContentLoaded listener, update the if block:<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!isIntersectionObserverSupported()) {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"\u26a0\ufe0f Intersection Observer not supported in this browser\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> statusElement = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"observer-status\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ NOW THIS WILL WORK:<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (statusElement) updateStatus(statusElement, <span class=\"hljs-string\">\"Browser not supported\"<\/span>, <span class=\"hljs-string\">\"error\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> distanceElem = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"observer-distance\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (distanceElem) distanceElem.textContent = <span class=\"hljs-string\">\"-\"<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span>;\u00a0\n\n\u00a0 }<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><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\n\n<p>Next, add the <code>updateDistanceDisplay<\/code> function. This will calculate the distance of the video from the viewport and update the corresponding element on your page, visually indicating when the video is approaching the loading trigger point.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateDistanceDisplay<\/span>(<span class=\"hljs-params\">rect, distanceElement<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!distanceElement || !rect) <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> windowHeight = <span class=\"hljs-built_in\">window<\/span>.innerHeight;\n\n\u00a0 <span class=\"hljs-keyword\">let<\/span> distance;\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (rect.top &gt; windowHeight) {\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Element is below viewport<\/span>\n\n\u00a0 \u00a0 distance = <span class=\"hljs-built_in\">Math<\/span>.floor(rect.top - windowHeight);\n\n\u00a0 } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (rect.bottom &lt; <span class=\"hljs-number\">0<\/span>) {\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Element is above viewport (scrolled past)<\/span>\n\n\u00a0 \u00a0 distance = <span class=\"hljs-built_in\">Math<\/span>.floor(<span class=\"hljs-built_in\">Math<\/span>.abs(rect.top));\u00a0\n\n\u00a0 } <span class=\"hljs-keyword\">else<\/span> {\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Element is in viewport<\/span>\n\n\u00a0 \u00a0 distance = <span class=\"hljs-number\">0<\/span>;\n\n\u00a0 }\n\n\u00a0 distanceElement.textContent = distance &gt; <span class=\"hljs-number\">0<\/span> ? <span class=\"hljs-string\">`<span class=\"hljs-subst\">${distance}<\/span>px`<\/span> : <span class=\"hljs-string\">\"In viewport\"<\/span>;\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Update color based on distance<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (distance &lt;= <span class=\"hljs-number\">200<\/span> &amp;&amp; distance &gt; <span class=\"hljs-number\">0<\/span>) {\n\n\u00a0 \u00a0 distanceElement.style.color = <span class=\"hljs-string\">\"#f59e0b\"<\/span>; <span class=\"hljs-comment\">\/\/ Orange when close<\/span>\n\n\u00a0 } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (distance === <span class=\"hljs-number\">0<\/span>) {\n\n\u00a0 \u00a0 distanceElement.style.color = <span class=\"hljs-string\">\"#10b981\"<\/span>; <span class=\"hljs-comment\">\/\/ Green when in viewport<\/span>\n\n\u00a0 } <span class=\"hljs-keyword\">else<\/span> {\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Reset to default\/inherit color (or a specific gray if preferred)<\/span>\n\n\u00a0 \u00a0 distanceElement.style.color = <span class=\"hljs-string\">\"\"<\/span>;\u00a0\n\n\u00a0 }\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><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\n\n<p>Using the Intersection Observer, add the <code>setupIntersectionObserver<\/code> function to <code>js\/main.js<\/code>; This function will find your video and status elements, then create an <code>IntersectionObserver<\/code> instance. The observer will watch your video and trigger actions when it enters the specified margin around the viewport.<\/p>\n\n\n<pre class=\"wp-block-code\" 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-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setupIntersectionObserver<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> lazyVideo = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">\".lazy-video\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> statusElement = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"observer-status\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> distanceElement = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"observer-distance\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!lazyVideo) {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"No lazy video found\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (statusElement) updateStatus(statusElement, <span class=\"hljs-string\">\"Video element not found\"<\/span>, <span class=\"hljs-string\">\"error\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Initial status update, assuming video and status elements are found.<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (statusElement) updateStatus(statusElement, <span class=\"hljs-string\">\"Not loaded\"<\/span>, <span class=\"hljs-string\">\"not-loaded\"<\/span>);\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Configuration for the observer<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> options = {\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">root<\/span>: <span class=\"hljs-literal\">null<\/span>, <span class=\"hljs-comment\">\/\/ Use viewport as root<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">rootMargin<\/span>: <span class=\"hljs-string\">\"200px 0px 200px 0px\"<\/span>, <span class=\"hljs-comment\">\/\/ Trigger 200px before entering viewport (top or bottom)<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">threshold<\/span>: <span class=\"hljs-number\">0<\/span>, <span class=\"hljs-comment\">\/\/ Trigger as soon as any part enters the margin area<\/span>\n\n\u00a0 };\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Create the Intersection Observer<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> observer = <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(<span class=\"hljs-function\">(<span class=\"hljs-params\">entries, observerInstance<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 entries.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">entry<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Update distance display when an intersection event occurs<\/span>\n\n\u00a0 \u00a0 \u00a0 updateDistanceDisplay(entry.boundingClientRect, distanceElement);\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (entry.isIntersecting) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\ud83d\udccd Video entering load zone (200px from viewport)\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 loadVideo(entry.target, statusElement); <span class=\"hljs-comment\">\/\/ You'll define loadVideo next<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 observerInstance.unobserve(entry.target); <span class=\"hljs-comment\">\/\/ Stop observing once triggered<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId) { <span class=\"hljs-comment\">\/\/ Stop continuous tracker if it's running<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 clearInterval(<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId);\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId = <span class=\"hljs-literal\">null<\/span>;\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 });\n\n\u00a0 }, options);\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Start observing the video<\/span>\n\n\u00a0 observer.observe(lazyVideo);\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Set up a continuous distance tracker for better UX before intersection<\/span>\n\n\u00a0 setupDistanceTracker(lazyVideo, distanceElement); <span class=\"hljs-comment\">\/\/ You'll define this later<\/span>\n\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\n\n<p>When the Intersection Observer determines it&#8217;s time to load the video, the <code>loadVideo<\/code> function will be called. This function dynamically creates the <code>&lt;source&gt;<\/code> element for your video, attaches it, and handles events such as successful loading or errors. The <code>attemptAutoplay<\/code> function is then called to try to play the video.<\/p>\n\n\n\n<p>Add these two functions, <code>loadVideo<\/code> and <code>attemptAutoplay<\/code>, to your <code>js\/main.js<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" 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-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">loadVideo<\/span>(<span class=\"hljs-params\">video, statusElement<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> startTime = performance.now();\n\n\u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Loading...\"<\/span>, <span class=\"hljs-string\">\"loading\"<\/span>);\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\ud83d\udd04 Starting video load...\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> source = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">\"source\"<\/span>);\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Ensure this path is correct relative to your index.html file.<\/span>\n\n\u00a0 source.src = <span class=\"hljs-string\">\"videos\/demo-video.mp4\"<\/span>;\u00a0\n\n\u00a0 source.type = <span class=\"hljs-string\">\"video\/mp4\"<\/span>;\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Clear any existing sources (e.g., if this function were called multiple times on the same video)<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">while<\/span> (video.firstChild) {\n\n\u00a0 \u00a0 video.removeChild(video.firstChild);\n\n\u00a0 }\n\n\u00a0 video.appendChild(source);\n\n\u00a0 video.addEventListener(<span class=\"hljs-string\">\"loadeddata\"<\/span>, () =&gt; {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> loadTime = performance.now() - startTime;\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">`\u26a1 Video loaded in <span class=\"hljs-subst\">${loadTime.toFixed(<span class=\"hljs-number\">2<\/span>)}<\/span>ms`<\/span>);\n\n\u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Loaded\"<\/span>, <span class=\"hljs-string\">\"loaded\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (video.hasAttribute(<span class=\"hljs-string\">\"data-autoplay\"<\/span>)) {\n\n\u00a0 \u00a0 \u00a0 attemptAutoplay(video, statusElement);\n\n\u00a0 \u00a0 }\n\n\u00a0 });\n\n\u00a0 video.addEventListener(<span class=\"hljs-string\">\"error\"<\/span>, (e) =&gt; {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"\u274c Video failed to load:\"<\/span>, e);\n\n\u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Load failed\"<\/span>, <span class=\"hljs-string\">\"error\"<\/span>);\n\n\u00a0 });\n\n\u00a0 video.addEventListener(<span class=\"hljs-string\">\"canplaythrough\"<\/span>, () =&gt; {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\u2705 Video can play through without buffering\"<\/span>);\n\n\u00a0 });\n\n\u00a0 video.load(); <span class=\"hljs-comment\">\/\/ Important: tells the browser to load the video with the new source<\/span>\n\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">attemptAutoplay<\/span>(<span class=\"hljs-params\">video, statusElement<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\ud83c\udfb5 Attempting autoplay...\"<\/span>);\n\n\u00a0 video.play()\n\n\u00a0 \u00a0 .then(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\u25b6\ufe0f Autoplay successful\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Playing\"<\/span>, <span class=\"hljs-string\">\"playing\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 setupViewportPause(video, statusElement); <span class=\"hljs-comment\">\/\/ You'll define this optional function later<\/span>\n\n\u00a0 \u00a0 })\n\n\u00a0 \u00a0 .catch(<span class=\"hljs-function\">(<span class=\"hljs-params\">error<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"\u23f8\ufe0f Autoplay failed (browser policy):\"<\/span>, error.message);\n\n\u00a0 \u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Loaded (autoplay blocked)\"<\/span>, <span class=\"hljs-string\">\"loaded\"<\/span>);\n\n\u00a0 \u00a0 });\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\n\n<p>For an even smoother experience, you can add a function that updates the distance display more frequently than the Intersection Observer events alone. The <code>setupDistanceTracker<\/code> function uses a simple interval for this.<\/p>\n\n\n\n<p>Add <code>setupDistanceTracker<\/code> to your <code>js\/main.js<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setupDistanceTracker<\/span>(<span class=\"hljs-params\">video, distanceElement<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/ This interval updates distance for smooth feedback until intersection.<\/span>\n\n\u00a0 <span class=\"hljs-comment\">\/\/ It clears itself if the video is removed from DOM or when the observer triggers.<\/span>\n\n\u00a0 <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">track<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Stop if video is removed or the interval has been explicitly cleared<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-built_in\">document<\/span>.body.contains(video) || !<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId) {\u00a0\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId) clearInterval(<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId);\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId = <span class=\"hljs-literal\">null<\/span>; <span class=\"hljs-comment\">\/\/ Ensure ID is cleared<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 \u00a0 }\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> rect = video.getBoundingClientRect();\n\n\u00a0 \u00a0 updateDistanceDisplay(rect, distanceElement);\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Clear any existing interval before starting a new one<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId) {\n\n\u00a0 \u00a0 \u00a0 clearInterval(<span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId);\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Initial call to set distance immediately, then start the interval<\/span>\n\n\u00a0 track();\u00a0\n\n\u00a0 <span class=\"hljs-built_in\">window<\/span>.distanceTrackerIntervalId = setInterval(track, <span class=\"hljs-number\">100<\/span>); <span class=\"hljs-comment\">\/\/ Update ~10 times\/sec<\/span>\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><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\n\n<p>Optionally, to conserve resources and improve user experience, you can add functionality to pause the video when it&#8217;s scrolled out of view and resume it when it comes back. This requires another Intersection Observer.<\/p>\n\n\n\n<p>Add the <code>setupViewportPause<\/code> function. Remember to call this from <code>attemptAutoplay<\/code> if you want this behavior.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setupViewportPause<\/span>(<span class=\"hljs-params\">video, statusElement<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> pauseObserverOptions = {\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">root<\/span>: <span class=\"hljs-literal\">null<\/span>,\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">threshold<\/span>: <span class=\"hljs-number\">0.1<\/span>, <span class=\"hljs-comment\">\/\/ Trigger when less than 10% of the video is visible<\/span>\n\n\u00a0 };\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> pauseObserver = <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(<span class=\"hljs-function\">(<span class=\"hljs-params\">entries<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 entries.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">entry<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (!entry.isIntersecting &amp;&amp; !video.paused) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\u23f8\ufe0f Video paused (out of viewport)\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 video.pause();\n\n\u00a0 \u00a0 \u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Paused (out of view)\"<\/span>, <span class=\"hljs-string\">\"paused\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 } <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (entry.isIntersecting &amp;&amp; video.paused &amp;&amp; video.readyState &gt;= video.HAVE_FUTURE_DATA) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> currentStatus = statusElement ? statusElement.textContent.toLowerCase() : <span class=\"hljs-string\">\"\"<\/span>;\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Only resume if it was explicitly paused by this mechanism or was playing<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (currentStatus.includes(<span class=\"hljs-string\">\"paused\"<\/span>) || currentStatus.includes(<span class=\"hljs-string\">\"playing\"<\/span>)) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\u25b6\ufe0f Video resumed (back in viewport)\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 video.play()\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .then(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Playing\"<\/span>, <span class=\"hljs-string\">\"playing\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 })\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 .catch(<span class=\"hljs-function\"><span class=\"hljs-params\">e<\/span> =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"Resume failed:\"<\/span>, e.message);\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 updateStatus(statusElement, <span class=\"hljs-string\">\"Paused (resume failed)\"<\/span>, <span class=\"hljs-string\">\"paused\"<\/span>);\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 \u00a0 });\n\n\u00a0 \u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 });\n\n\u00a0 }, pauseObserverOptions);\n\n\u00a0 pauseObserver.observe(video);\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><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\n\n<p>With all these pieces in place, your <code>js\/main.js<\/code> file should now correctly implement lazy loading with detailed feedback using the Intersection Observer API.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\"><strong>Test Your Implementation&nbsp;<\/strong><\/h4>\n\n\n\n<p>Now, with your HTML, CSS, and JavaScript updated for Method 2:<\/p>\n\n\n\n<ol start=\"1\" class=\"wp-block-list\">\n<li>Open your <code>index.html<\/code> in a browser (preferably using your local development server).<\/li>\n\n\n\n<li>Look at the <strong>Status Display<\/strong> section on your page:\n<ul class=\"wp-block-list\">\n<li><strong>Status:<\/strong> Should initially show &#8220;Not loaded&#8221;.<\/li>\n\n\n\n<li><strong>Distance from viewport:<\/strong> Should show a pixel value, e.g., &#8220;850px&#8221;.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Scroll down slowly.\n<ul class=\"wp-block-list\">\n<li>Watch the &#8220;Distance from viewport&#8221; value decrease. Its color should change to orange when it&#8217;s 200px or less away.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>When the video is approximately 200px from entering the viewport:\n<ul class=\"wp-block-list\">\n<li>The <strong>Status<\/strong> should change to &#8220;Loading&#8230;&#8221;<\/li>\n\n\n\n<li>The <code>demo-video.mp4<\/code> file should appear in the Network tab and start downloading.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Once the video data is loaded:\n<ul class=\"wp-block-list\">\n<li>The <strong>Status<\/strong> should briefly change to &#8220;Loaded&#8221; and then to &#8220;Playing&#8221; as autoplay kicks in.<\/li>\n\n\n\n<li>The video in the <code>.video-container<\/code> should start playing automatically.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Scroll the playing video out of view and back into view to test the pause\/resume functionality (if <code>setupViewportPause<\/code> is active and working as intended).<\/li>\n<\/ol>\n\n\n\n<p>You should observe that <code>demo-video.mp4<\/code> only begins to load when you scroll close to it (specifically, when its top edge is <strong>200px<\/strong> away from the bottom of the viewport or its bottom edge is <strong>200px<\/strong> away from the top of the viewport due to the <code>rootMargin<\/code>).&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1753214142\/blog-How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Your_Core_Web_Vitals-3.gif\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Method 2: Intersection Observer API<\/figcaption><\/figure>\n\n\n\n<p>The status display provides clear, real-time feedback on this process, demonstrating the precision and control offered by the Intersection Observer API. This is a marked improvement over native lazy loading, especially for user experience and performance tuning.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Method 3: <\/strong><strong>Using Cloudinary SDK Integration<\/strong><\/h3>\n\n\n\n<p>While Methods 1 and 2 handle lazy loading well, they can&#8217;t optimize the video files themselves. This is where you can use Cloudinary to automatically deliver the smallest possible video file while maintaining visual quality, using AI-powered optimization that adapts to each user&#8217;s device and connection speed.<\/p>\n\n\n\n<p>Cloudinary automatically handles format selection (WebM vs. MP4), quality optimization, and responsive sizing, resulting in significantly smaller file sizes and faster loading times.<\/p>\n\n\n\n<p>Update your <code>index.html<\/code> file with <a href=\"https:\/\/gist.github.com\/Olanetsoft\/a344450510101afaf442bb368ce441d3\" target=\"_blank\" rel=\"noreferrer noopener\">this code snippet<\/a>, similar to the previous methods, but including elements to display Cloudinary&#8217;s real-time optimizations. Then add <a href=\"https:\/\/gist.github.com\/Olanetsoft\/b29ac63ec7f32b4acf0aa94ff01a0367\" target=\"_blank\" rel=\"noreferrer noopener\">these styles<\/a> to your <code>css\/styles.css<\/code>. The key addition is styling for the optimization display, which shows Cloudinary&#8217;s real-time improvements.<\/p>\n\n\n\n<p>Next, add this first block to your <code>js\/main.js<\/code>. This sets up the foundation and intersection observer:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ js\/main.js - Method 3: Cloudinary SDK Integration<\/span>\n\n<span class=\"hljs-built_in\">document<\/span>.addEventListener(<span class=\"hljs-string\">\"DOMContentLoaded\"<\/span>, <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\ud83c\udfac Method 3: Cloudinary SDK Integration loaded\"<\/span>);\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Initialize Cloudinary optimization when video container comes into view<\/span>\n\n\u00a0 setupCloudinaryLazyLoading();\n\n});\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">setupCloudinaryLazyLoading<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> videoContainer = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"cloudinary-video-container\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!videoContainer) {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.warn(<span class=\"hljs-string\">\"Video container not found\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Setup intersection observer to load video when it approaches viewport<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> options = {\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">root<\/span>: <span class=\"hljs-literal\">null<\/span>,\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">rootMargin<\/span>: <span class=\"hljs-string\">\"200px 0px\"<\/span>, <span class=\"hljs-comment\">\/\/ Same as Method 2 for comparison<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-attr\">threshold<\/span>: <span class=\"hljs-number\">0<\/span>,\n\n\u00a0 };\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> observer = <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(<span class=\"hljs-function\">(<span class=\"hljs-params\">entries<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 entries.forEach(<span class=\"hljs-function\">(<span class=\"hljs-params\">entry<\/span>) =&gt;<\/span> {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-keyword\">if<\/span> (entry.isIntersecting) {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(\n\n\u00a0 \u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">\"\ud83d\udccd Cloudinary video container in view, generating optimized video\"<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 );\n\n\u00a0 \u00a0 \u00a0 \u00a0 generateOptimizedVideo(); <span class=\"hljs-comment\">\/\/ We'll add this function next<\/span>\n\n\u00a0 \u00a0 \u00a0 \u00a0 observer.unobserve(entry.target);\n\n\u00a0 \u00a0 \u00a0 }\n\n\u00a0 \u00a0 });\n\n\u00a0 }, options);\n\n\u00a0 observer.observe(videoContainer);\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><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\n\n<p>This creates the same precise loading control as Method 2. The video will only start optimizing when it&#8217;s <strong>200px<\/strong> from the viewport. Test this by opening the console and scrolling for the &#8220;video container in view&#8221; message.<\/p>\n\n\n\n<p>Add these two functions below to js\/main.js to detect the user&#8217;s connection speed and determine optimal video quality:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getConnectionType<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> connection =\n\n\u00a0 \u00a0 navigator.connection ||\n\n\u00a0 \u00a0 navigator.mozConnection ||\n\n\u00a0 \u00a0 navigator.webkitConnection;\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> connection ? connection.effectiveType : <span class=\"hljs-string\">\"4g\"<\/span>;\n\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getOptimalQuality<\/span>(<span class=\"hljs-params\">connectionType<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Cloudinary's AI automatically optimizes, but we can provide hints<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> qualityMap = {\n\n\u00a0 \u00a0 <span class=\"hljs-string\">\"slow-2g\"<\/span>: <span class=\"hljs-number\">40<\/span>, <span class=\"hljs-comment\">\/\/ Very low quality for slow connections<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-string\">\"2g\"<\/span>: <span class=\"hljs-number\">50<\/span>, <span class=\"hljs-comment\">\/\/ Low quality for 2G<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-string\">\"3g\"<\/span>: <span class=\"hljs-number\">70<\/span>, <span class=\"hljs-comment\">\/\/ Medium quality for 3G<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-string\">\"4g\"<\/span>: <span class=\"hljs-string\">\"auto\"<\/span>, <span class=\"hljs-comment\">\/\/ Let Cloudinary's AI decide the best quality<\/span>\n\n\u00a0 };\n\n\u00a0 <span class=\"hljs-keyword\">return<\/span> qualityMap&#91;connectionType] || <span class=\"hljs-string\">\"auto\"<\/span>;\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><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\n\n<p>Cloudinary will automatically adapt video quality based on the user&#8217;s connection speed. A user on slow 2G gets a 40-quality video, while 4G users get AI-optimized quality.<\/p>\n\n\n\n<p>Next, you\u2019ll generate the optimized video with Cloudinary. Create a function called <code>generateOptimizedVideo<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">generateOptimizedVideo<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Check if Cloudinary SDK is loaded<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">typeof<\/span> cloudinary === <span class=\"hljs-string\">\"undefined\"<\/span>) {\n\n\u00a0 \u00a0 updateStatus(<span class=\"hljs-string\">\"SDK not loaded\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"\u274c Cloudinary SDK not loaded\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 }\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Detect user's connection for optimal quality<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> connection = getConnectionType();\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> quality = getOptimalQuality(connection);\n\n\u00a0 updateConnectionDisplay(connection); <span class=\"hljs-comment\">\/\/ We'll add this next<\/span>\n\n\u00a0 updateQualityDisplay(quality); <span class=\"hljs-comment\">\/\/ We'll add this next<\/span>\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Generate optimized video with Cloudinary transformations<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> videoTag = cloudinary\n\n\u00a0 \u00a0 .videoTag(<span class=\"hljs-string\">\"brand-portal-hero_dwdezz\"<\/span>, {\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Use your actual video<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">quality<\/span>: quality, <span class=\"hljs-comment\">\/\/ Auto-optimize quality based on connection<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">format<\/span>: <span class=\"hljs-string\">\"auto\"<\/span>, <span class=\"hljs-comment\">\/\/ WebM for Chrome, MP4 for Safari<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-string\">\"auto\"<\/span>, <span class=\"hljs-comment\">\/\/ Responsive sizing<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">dpr<\/span>: <span class=\"hljs-string\">\"auto\"<\/span>, <span class=\"hljs-comment\">\/\/ High-DPI display support<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">crop<\/span>: <span class=\"hljs-string\">\"scale\"<\/span>, <span class=\"hljs-comment\">\/\/ Maintain aspect ratio<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">loading<\/span>: <span class=\"hljs-string\">\"lazy\"<\/span>, <span class=\"hljs-comment\">\/\/ Built-in lazy loading<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">controls<\/span>: <span class=\"hljs-literal\">true<\/span>,\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">muted<\/span>: <span class=\"hljs-literal\">true<\/span>,\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">poster<\/span>: {\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">format<\/span>: <span class=\"hljs-string\">\"jpg\"<\/span>,\n\n\u00a0 \u00a0 \u00a0 \u00a0 <span class=\"hljs-attr\">quality<\/span>: <span class=\"hljs-string\">\"auto\"<\/span>,\n\n\u00a0 \u00a0 \u00a0 },\n\n\u00a0 \u00a0 })\n\n\u00a0 \u00a0 .toHtml(); <span class=\"hljs-comment\">\/\/ CRITICAL: Convert VideoTag object to HTML string<\/span>\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Insert optimized video into container<\/span>\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> container = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"cloudinary-video-container\"<\/span>);\n\n\u00a0 container.innerHTML = videoTag;\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Update transformation URL display<\/span>\n\n\u00a0 updateTransformationDisplay(cloudinary, quality); <span class=\"hljs-comment\">\/\/ We'll add this next<\/span>\n\n\u00a0 <span class=\"hljs-comment\">\/\/ Monitor the optimized video<\/span>\n\n\u00a0 monitorOptimizedVideo(container.querySelector(<span class=\"hljs-string\">\"video\"<\/span>)); <span class=\"hljs-comment\">\/\/ We'll add this next<\/span>\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\u2705 Cloudinary optimized video generated\"<\/span>);\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><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\n\n<p>The code snippet above is the core of Cloudinary&#8217;s power. The <code>videoTag()<\/code> call automatically handles format selection, quality optimization, responsive sizing, and lazy loading. The <code>.toHtml()<\/code> method is also important; it converts Cloudinary&#8217;s <code>VideoTag<\/code> object into actual HTML that can be inserted into the DOM.<\/p>\n\n\n\n<p>Next, add these functions to update the optimization display in real time:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateConnectionDisplay<\/span>(<span class=\"hljs-params\">connection<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> element = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"connection-type\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (element) {\n\n\u00a0 \u00a0 element.textContent = connection.toUpperCase();\n\n\u00a0 }\n\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateQualityDisplay<\/span>(<span class=\"hljs-params\">quality<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> element = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"applied-quality\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (element) {\n\n\u00a0 \u00a0 element.textContent = quality === <span class=\"hljs-string\">\"auto\"<\/span> ? <span class=\"hljs-string\">\"AI-Optimized\"<\/span> : quality;\n\n\u00a0 }\n\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateTransformationDisplay<\/span>(<span class=\"hljs-params\">cloudinary, quality<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> element = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"transformation-url\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (element) {\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Show the transformation parameters being applied<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> baseUrl = <span class=\"hljs-string\">`https:\/\/res.cloudinary.com\/<span class=\"hljs-subst\">${CLOUDINARY_CONFIG.cloudName}<\/span>\/video\/upload`<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> transforms = &#91;\n\n\u00a0 \u00a0 \u00a0 quality === <span class=\"hljs-string\">\"auto\"<\/span> ? <span class=\"hljs-string\">\"q_auto\"<\/span> : <span class=\"hljs-string\">`q_<span class=\"hljs-subst\">${quality}<\/span>`<\/span>,\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">\"f_auto\"<\/span>, \u00a0 \u00a0<span class=\"hljs-comment\">\/\/ Automatic format selection<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">\"w_auto\"<\/span>, \u00a0 \u00a0<span class=\"hljs-comment\">\/\/ Responsive width<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">\"dpr_auto\"<\/span>, \u00a0<span class=\"hljs-comment\">\/\/ Device pixel ratio optimization<\/span>\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">\"c_scale\"<\/span>, \u00a0 <span class=\"hljs-comment\">\/\/ Scale to maintain aspect ratio<\/span>\n\n\u00a0 \u00a0 ].join(<span class=\"hljs-string\">\",\"<\/span>);\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> fullUrl = <span class=\"hljs-string\">`<span class=\"hljs-subst\">${baseUrl}<\/span>\/<span class=\"hljs-subst\">${transforms}<\/span>\/brand-portal-hero_dwdezz`<\/span>;\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Transformation URL:\"<\/span>, fullUrl);\n\n\u00a0 \u00a0 element.textContent = fullUrl;\n\n\u00a0 }\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><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\n\n<p>The functions added above provide visual feedback showing users precisely what optimizations Cloudinary applies: connection detection, quality selection, and the transformation URL with all parameters. The <code>console.log<\/code> helps you understand what&#8217;s happening.<\/p>\n\n\n\n<p>Finally, create the following functions to monitor the optimized video and show file size savings:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">monitorOptimizedVideo<\/span>(<span class=\"hljs-params\">video<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (!video) <span class=\"hljs-keyword\">return<\/span>;\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> startTime = performance.now();\n\n\u00a0 video.addEventListener(<span class=\"hljs-string\">\"loadeddata\"<\/span>, () =&gt; {\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> loadTime = performance.now() - startTime;\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.log(\n\n\u00a0 \u00a0 \u00a0 <span class=\"hljs-string\">`\u26a1 Cloudinary optimized video loaded in <span class=\"hljs-subst\">${loadTime.toFixed(<span class=\"hljs-number\">2<\/span>)}<\/span>ms`<\/span>\n\n\u00a0 \u00a0 );\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ Calculate approximate file size savings (demo purposes)<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-comment\">\/\/ In production, you'd compare with original file size<\/span>\n\n\u00a0 \u00a0 <span class=\"hljs-keyword\">const<\/span> estimatedSavings = <span class=\"hljs-built_in\">Math<\/span>.floor(<span class=\"hljs-built_in\">Math<\/span>.random() * <span class=\"hljs-number\">30<\/span>) + <span class=\"hljs-number\">50<\/span>; <span class=\"hljs-comment\">\/\/ 50-80% savings<\/span>\n\n\u00a0 \u00a0 updateSizeReduction(estimatedSavings);\n\n\u00a0 });\n\n\u00a0 video.addEventListener(<span class=\"hljs-string\">\"error\"<\/span>, (e) =&gt; {\n\n\u00a0 \u00a0 <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"\u274c Cloudinary video error:\"<\/span>, e);\n\n\u00a0 });\n\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateSizeReduction<\/span>(<span class=\"hljs-params\">percentage<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-keyword\">const<\/span> element = <span class=\"hljs-built_in\">document<\/span>.getElementById(<span class=\"hljs-string\">\"size-reduction\"<\/span>);\n\n\u00a0 <span class=\"hljs-keyword\">if<\/span> (element) {\n\n\u00a0 \u00a0 element.textContent = <span class=\"hljs-string\">`-<span class=\"hljs-subst\">${percentage}<\/span>%`<\/span>;\n\n\u00a0 \u00a0 element.style.color = <span class=\"hljs-string\">\"#10b981\"<\/span>;\n\n\u00a0 }\n\n}\n\n<span class=\"hljs-comment\">\/\/ Helper function for status updates<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">updateStatus<\/span>(<span class=\"hljs-params\">status<\/span>) <\/span>{\n\n\u00a0 <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"\ud83d\udcca Status:\"<\/span>, status);\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><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\n\n<p>The above code helps monitor the video loading performance and simulates the reduction of file size. You can compare the original file size to show genuine savings in production.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Test Your Implementation<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1753214142\/blog-How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Your_Core_Web_Vitals-4.gif\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Test Your Implementation &#8211; Cloudinary SDK<\/figcaption><\/figure>\n\n\n\n<p>You should observe several key improvements in the console and on-screen displays. The console shows connection detection and optimization parameters, while the video loads with automatic format selection (WebM\/MP4\/OGV sources).&nbsp;<\/p>\n\n\n\n<p>The real-time display reveals exactly which optimizations Cloudinary applies. Most importantly, file sizes are significantly smaller compared to Methods 1 and 2, demonstrating why intelligent optimization is crucial for video performance.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Performance Testing With Lighthouse and PageSpeed Insights<\/strong><\/h2>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1753214125\/blog-How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Your_Core_Web_Vitals-5.png\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Performance Testing with Lighthouse and PageSpeed Insights<\/figcaption><\/figure>\n\n\n\n<p>Consider testing your optimized videos using Google&#8217;s performance tools for a comprehensive performance assessment. Run <a target=\"_blank\" href=\"https:\/\/developer.chrome.com\/docs\/lighthouse\/overview\" rel=\"noreferrer noopener\">Lighthouse in Chrome DevTools<\/a> or analyze your page with PageSpeed Insights to get detailed Core Web Vitals metrics. Pay particular attention to Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and First Input Delay (FID), and check that video processing doesn&#8217;t block user interactions.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Monitor Video-Specific Metrics<\/strong><\/h3>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1753214123\/blog-How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Your_Core_Web_Vitals-6.png\" alt=\"\"\/><figcaption class=\"wp-element-caption\">Monitoring Video-Specific Metrics<\/figcaption><\/figure>\n\n\n\n<p>Use the <strong>Performance<\/strong> tab in Chrome DevTools to track video loading behavior and identify any bottlenecks. Look for the video element&#8217;s impact on LCP timing and verify that lazy loading works correctly by observing network requests as you scroll. These performance insights prove why intelligent optimization is crucial for video performance and help you fine-tune your implementation for optimal Core Web Vitals scores.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>You&#8217;ve built and tested a complete <a href=\"https:\/\/cloudinary.com\/guides\/web-performance\/video-optimization-why-you-need-it-and-5-critical-best-practices\">video optimization<\/a> system step by step. We showcased three types of lazy loading, implemented Cloudinary&#8217;s video transformations, and followed best practices to implement fast-loading, high-performing video. The results are videos that maintain excellent Core Web Vitals scores and user experience.<\/p>\n\n\n\n<p>Sign up for <a target=\"_blank\" href=\"https:\/\/cloudinary.com\/users\/register\/free\" rel=\"noreferrer noopener\">Cloudinary<\/a> today to optimize video delivery with smart loading and autoplay controls.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/video_transformation_reference\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary Video Transformation Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/web.dev\/vitals\/\" target=\"_blank\" rel=\"noreferrer noopener\">Web Vitals Measurement Guide<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Intersection_Observer_API\" target=\"_blank\" rel=\"noreferrer noopener\">Intersection Observer API Reference<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/web.dev\/fast\/#optimize-your-media\" target=\"_blank\" rel=\"noreferrer noopener\">Video Optimization Best Practices<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/web.dev\/vitals-tools\/\" target=\"_blank\" rel=\"noreferrer noopener\">Core Web Vitals Tools<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Autoplay videos can work exceptionally well in specific contexts. Want a muted promotional banner to showcase your products? An interactive product tour? A hero section that shares your brand at a glance? When set up correctly, autoplay videos increase engagement, as users can watch your content without the extra step of clicking. The challenge is [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":38020,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[186,227,303],"class_list":["post-38019","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-lazy-loading","tag-performance-optimization","tag-video"],"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>How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals<\/title>\n<meta name=\"description\" content=\"Poorly optimized videos can tank your Core Web Vitals. Learn how to lazy load and autoplay videos responsibly while keeping performance scores high.\" \/>\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\/lazy-load-autoplay-videos-core-web-vitals\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals\" \/>\n<meta property=\"og:description\" content=\"Poorly optimized videos can tank your Core Web Vitals. Learn how to lazy load and autoplay videos responsibly while keeping performance scores high.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2025-07-23T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-10-31T19:57:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.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\/lazy-load-autoplay-videos-core-web-vitals#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals\",\"datePublished\":\"2025-07-23T14:00:00+00:00\",\"dateModified\":\"2025-10-31T19:57:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals\"},\"wordCount\":2318,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA\",\"keywords\":[\"Lazy Loading\",\"Performance Optimization\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2025\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals\",\"url\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals\",\"name\":\"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA\",\"datePublished\":\"2025-07-23T14:00:00+00:00\",\"dateModified\":\"2025-10-31T19:57:54+00:00\",\"description\":\"Poorly optimized videos can tank your Core Web Vitals. Learn how to lazy load and autoplay videos responsibly while keeping performance scores high.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals\"}]},{\"@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":"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals","description":"Poorly optimized videos can tank your Core Web Vitals. Learn how to lazy load and autoplay videos responsibly while keeping performance scores high.","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\/lazy-load-autoplay-videos-core-web-vitals","og_locale":"en_US","og_type":"article","og_title":"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals","og_description":"Poorly optimized videos can tank your Core Web Vitals. Learn how to lazy load and autoplay videos responsibly while keeping performance scores high.","og_url":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals","og_site_name":"Cloudinary Blog","article_published_time":"2025-07-23T14:00:00+00:00","article_modified_time":"2025-10-31T19:57:54+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.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\/lazy-load-autoplay-videos-core-web-vitals#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals","datePublished":"2025-07-23T14:00:00+00:00","dateModified":"2025-10-31T19:57:54+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals"},"wordCount":2318,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA","keywords":["Lazy Loading","Performance Optimization","Video"],"inLanguage":"en-US","copyrightYear":"2025","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals","url":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals","name":"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA","datePublished":"2025-07-23T14:00:00+00:00","dateModified":"2025-10-31T19:57:54+00:00","description":"Poorly optimized videos can tank your Core Web Vitals. Learn how to lazy load and autoplay videos responsibly while keeping performance scores high.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/lazy-load-autoplay-videos-core-web-vitals#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to Lazy Load and Autoplay Videos Without Killing Your Core Web Vitals"}]},{"@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\/v1752857488\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals\/Blog_How_to_Lazy_Load_and_Autoplay_Videos_Without_Killing_Core_Web_Vitals.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38019","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=38019"}],"version-history":[{"count":3,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38019\/revisions"}],"predecessor-version":[{"id":39048,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/38019\/revisions\/39048"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/38020"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=38019"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=38019"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=38019"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}