{"id":36154,"date":"2024-10-23T07:00:00","date_gmt":"2024-10-23T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=36154"},"modified":"2024-10-23T11:20:59","modified_gmt":"2024-10-23T18:20:59","slug":"optimized-image-custom-element-web-components","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components","title":{"rendered":"Building an Optimized Cloudinary Image Custom Element With Web Components"},"content":{"rendered":"\n<p>Every developer knows the benefits of code reusability. It saves time and ensures consistency..<\/p>\n\n\n\n<p><a href=\"https:\/\/www.webcomponents.org\/introduction\">Web components<\/a> (WC) allow you to create reusable and interoperable custom elements that are encapsulated, so they\u2019re more easily integrated into other projects regardless of framework. Web components work with all major browsers, though with varying degrees of support.<\/p>\n\n\n\n<p>In this blog post, I\u2019ll demonstrate how you can create a reusable and lightweight web component for viewing images from Cloudinary with support for transformations and lazy loading.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lit<\/h2>\n\n\n\n<p>Although having web components as a standard for your browsers is great, frameworks and libraries provide many more niceties. <a href=\"https:\/\/lit.dev\/\">Lit<\/a> helps alleviate this by adding additional features such as HTML templates, embedded JavaScript expressions, and easier attribute binding, streamlining the web development experience.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Boilerplate<\/h2>\n\n\n\n<p>Let\u2019s make a barebones web component with Lit that can render a Cloudinary image. We\u2019ll add features to it one at a time throughout this blog post. This initial version will only render images and nothing else.<\/p>\n\n\n\n<p>First, let\u2019s install Lit using <code>npm<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">$ npm i lit<\/code><\/span><\/pre>\n\n\n<p>With Lit installed, we\u2019ll make our web component live in its own file called <code>cdl-image.js<\/code> (we\u2019re effectively making a minimal version of <a href=\"https:\/\/next.cloudinary.dev\/cldimage\/examples\"><code>CldImage<\/code><\/a>):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { LitElement, css, html, nothing } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/node_modules\/lit\/index.js\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">CdlImage<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">LitElement<\/span> <\/span>{\n\n\u00a0\u00a0<span class=\"hljs-keyword\">static<\/span> styles = css`<span class=\"css\">\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-selector-pseudo\">:host<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">display<\/span>: inline-block;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0`<\/span>;\n\n\u00a0\u00a0<span class=\"hljs-keyword\">static<\/span> properties = {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">baseUrl<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">src<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">alt<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0};\n\n\u00a0\u00a0render() {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">return<\/span> html`<span class=\"xml\">\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">src<\/span>=<\/span><\/span><span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.src ? <span class=\"hljs-keyword\">this<\/span>._buildImageUrl() : nothing}<\/span><span class=\"xml\"><span class=\"hljs-tag\">\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">alt<\/span>=<\/span><\/span><span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.alt ?? nothing}<\/span><span class=\"xml\"><span class=\"hljs-tag\">\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0`<\/span>;\n\n\u00a0\u00a0}\n\n\u00a0\u00a0<span class=\"hljs-comment\">\/**\n\n\u00a0\u00a0\u00a0* Builds an image URL to Cloudinary using the custom element attributes.\n\n\u00a0\u00a0\u00a0*\/<\/span>\n\n\u00a0\u00a0_buildImageUrl() {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-comment\">\/\/ Strip trailing slash if one exists.<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">const<\/span> baseUrl = <span class=\"hljs-keyword\">this<\/span>.baseUrl.endsWith(<span class=\"hljs-string\">\"\/\"<\/span>)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0? <span class=\"hljs-keyword\">this<\/span>.baseUrl.slice(<span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">-1<\/span>)\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0: <span class=\"hljs-keyword\">this<\/span>.baseUrl;\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`<span class=\"hljs-subst\">${baseUrl}<\/span>\/<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.src}<\/span>`<\/span>;\n\n\u00a0\u00a0}\n\n}\n\n<span class=\"hljs-built_in\">window<\/span>.customElements.define(<span class=\"hljs-string\">\"cdl-image\"<\/span>, CdlImage);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then, to utilize the web component, we\u2019ll create an index.html file to fetch that script and use the component:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-meta\">&lt;!DOCTYPE <span class=\"hljs-meta-keyword\">html<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">html<\/span>&gt;<\/span>\n\n\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span>Cloudinary Image Web Component<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"module\"<\/span> <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\".\/boilerplate.js\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n\n\u00a0\u00a0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">head<\/span>&gt;<\/span>\n\n\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span>&gt;<\/span>Cloudinary Image Web Component Demo<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">cdl-image<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">baseUrl<\/span>=<span class=\"hljs-string\">\"https:\/\/res.cloudinary.com\/demo\"<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">\"cld-sample-5\"<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"a white tennis shoe with a blue insole\"<\/span>\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">cdl-image<\/span>&gt;<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n\n\u00a0\u00a0<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">body<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">html<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>For <code>baseURL<\/code>, you\u2019ll want to use your own if you have your own image assets. Otherwise, <code>demo<\/code> works fine. With that, you have everything you need to render an image.<\/p>\n\n\n\n<p>Build systems are out of scope for this blog post, but I\u2019d recommend using <a href=\"https:\/\/vitejs.dev\/\">Vite<\/a> to get this example working, though you\u2019re free to use whatever build tool you like:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">$ npm i -D vite<\/code><\/span><\/pre>\n\n\n<p>Run Vite by executing <code>npx vite<\/code> in the root of your project, which will make the example accessible on <code>http:\/\/localhost:5173\/<\/code>. Navigating there should result in a picture of a large tennis shoe.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Transformations<\/strong><\/h2>\n\n\n\n<p>Let\u2019s add support for the most common image transformations, that being width, height, format, crop, zoom, and gravity.<\/p>\n\n\n\n<p>First, we need to add those transformation properties as fields on our web component:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">static<\/span> properties = {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">baseUrl<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">src<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">alt<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">width<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Number<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">height<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Number<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">format<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">crop<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">zoom<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Number<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">gravity<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">String<\/span> },\n\n};<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">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>Our URL-building method will have to be updated to use these transformations as well. We can modify it to check if they\u2019ve been set on the component, and if so, add them to a comma-delimited list of transformations in the URL:<\/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\">\/**\n\n\u00a0* Builds an image URL to Cloudinary using the custom element attributes.\n\n\u00a0*\/<\/span>\n\n_buildImageUrl() {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">const<\/span> transforms = &#91;];\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-comment\">\/\/ Add attribute specified transformations to the URL.<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.width != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`w_<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">Math<\/span>.round(<span class=\"hljs-keyword\">this<\/span>.width)}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.height != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`h_<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">Math<\/span>.round(<span class=\"hljs-keyword\">this<\/span>.height)}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.format != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`f_<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.format}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.crop != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`c_<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.crop}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.zoom != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`z_<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.zoom}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>.gravity != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`g_<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.gravity}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-comment\">\/\/ Strip trailing slash if one exists.<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">const<\/span> baseUrl = <span class=\"hljs-keyword\">this<\/span>.baseUrl.endsWith(<span class=\"hljs-string\">'\/'<\/span>) ?\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>.baseUrl.slice(<span class=\"hljs-number\">0<\/span>, <span class=\"hljs-number\">-1<\/span>) :\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>.baseUrl;\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-string\">`<span class=\"hljs-subst\">${baseUrl}<\/span>\/<span class=\"hljs-subst\">${transforms.join(<span class=\"hljs-string\">','<\/span>)}<\/span>\/<span class=\"hljs-subst\">${<span class=\"hljs-keyword\">this<\/span>.src}<\/span>`<\/span>;\n\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Also, don\u2019t forget to pass width and height to the <code>img<\/code> element as attributes, and with that, you can now use these transformations. Try them out!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>IntersectionObserver<\/strong><\/h2>\n\n\n\n<p>The magic behind lazy loading images only when the user can see them comes from <code>IntersectionObserver<\/code>. This class allows a callback to be called whenever an element comes into view in the viewport. It supports a bit more options than that, but we don\u2019t need to concern ourselves with those options for our specific use case.<\/p>\n\n\n\n<p>First, let\u2019s define some new state that we\u2019ll need:<\/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-keyword\">static<\/span> properties = {\n\n\u00a0\u00a0\u00a0\u00a0...\n\n\u00a0\u00a0\u00a0\u00a0_observerInitialized: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">state<\/span>: <span class=\"hljs-literal\">true<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">_hasBeenInViewport<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">state<\/span>: <span class=\"hljs-literal\">true<\/span> },\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attr\">_imageLoaded<\/span>: { <span class=\"hljs-attr\">type<\/span>: <span class=\"hljs-built_in\">Boolean<\/span>, <span class=\"hljs-attr\">state<\/span>: <span class=\"hljs-literal\">true<\/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\n<p>We should create the <code>IntersectionObserver<\/code> whenever our custom element gets connected to the DOM like so:<\/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\">connectedCallback() {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">super<\/span>.connectedCallback();\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observer = <span class=\"hljs-keyword\">new<\/span> IntersectionObserver(<span class=\"hljs-keyword\">this<\/span>._onImageObserved.bind(<span class=\"hljs-keyword\">this<\/span>));\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observer.observe(<span class=\"hljs-keyword\">this<\/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>Note that the callback passed into it is a method called <code>_onImageObserved<\/code>. We\u2019ll define that here in a bit. Passing this into the observe method call tells the observer that we\u2019re waiting for our custom element to become visible on the screen.<\/p>\n\n\n\n<p>Let\u2019s also add some cleanup code that removes the observer whenever the element is removed from the DOM so we don\u2019t accidentally leave the observer in memory after the image element is removed.<\/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\">disconnectedCallback() {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">super<\/span>.disconnectedCallback();\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>._observer != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observer.unobserve(<span class=\"hljs-keyword\">this<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observer = <span class=\"hljs-literal\">null<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n}<\/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>The <code>_onImageObserved<\/code> is where the magic happens.<\/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\">\/**\n\n\u00a0* <span class=\"hljs-doctag\">@type <span class=\"hljs-type\">{IntersectionObserverCallback}<\/span><\/span>\n\n\u00a0*\/<\/span>\n\n_onImageObserved(entries) {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (entries.some(<span class=\"hljs-function\">(<span class=\"hljs-params\">entry<\/span>) =&gt;<\/span> entry.isIntersecting)) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observer.unobserve(<span class=\"hljs-keyword\">this<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observer = <span class=\"hljs-literal\">null<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._hasBeenInViewport = <span class=\"hljs-literal\">true<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._observerInitialized = <span class=\"hljs-literal\">true<\/span>;\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>This function iterates through all the entries in the observer (we should only have one) and checks if our image element has appeared on the screen yet. This callback gets run both when the observer is first created and when an intersection happens. That\u2019s what we need to check the <code>isIntersecting<\/code> property.<\/p>\n\n\n\n<p>We also set some flags so we can apply CSS classes dynamically, and also to assist with cleaning up the observable as we no longer need it for this specific image.<\/p>\n\n\n\n<p>The URL builder also needs to be updated so that we fetch a lower resolution version if it\u2019s called before the intersection has happened, which will happen at page load.<\/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-keyword\">const<\/span> LAZY_LOAD_SIZE = <span class=\"hljs-number\">32<\/span>;\n\n...\n\n<span class=\"hljs-comment\">\/**\n\n\u00a0* Builds an image URL to Cloudinary using the custom element attributes.\n\n\u00a0*\/<\/span>\n\n_buildImageUrl() {\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">const<\/span> transforms = &#91;];\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">let<\/span> width = <span class=\"hljs-keyword\">this<\/span>.width;\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">let<\/span> height = <span class=\"hljs-keyword\">this<\/span>.height;\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-comment\">\/\/ Calculate a shrunken width and height to send to Cloudinary if the<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-comment\">\/\/ image hasn't been in the viewport yet.<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (!<span class=\"hljs-keyword\">this<\/span>._hasBeenInViewport) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (width != <span class=\"hljs-literal\">null<\/span> &amp;&amp; height != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">const<\/span> ratio = LAZY_LOAD_SIZE \/ <span class=\"hljs-keyword\">this<\/span>.width;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0width = LAZY_LOAD_SIZE;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0height = <span class=\"hljs-keyword\">this<\/span>.height * ratio;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} <span class=\"hljs-keyword\">else<\/span> <span class=\"hljs-keyword\">if<\/span> (height != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0height = LAZY_LOAD_SIZE;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} <span class=\"hljs-keyword\">else<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0width = LAZY_LOAD_SIZE;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-comment\">\/\/ Add attribute specified transformations to the URL.<\/span>\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (width != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`w_<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">Math<\/span>.round(width)}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (height != <span class=\"hljs-literal\">null<\/span>) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0transforms.push(<span class=\"hljs-string\">`h_<span class=\"hljs-subst\">${<span class=\"hljs-built_in\">Math<\/span>.round(height)}<\/span>`<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0...\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>If we set either just the width or the height we can replace those and Cloudinary will shrink the other dimension to keep the aspect ratio; however, if both are being set, then we need to calculate that ourselves.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Blur Transition<\/strong><\/h2>\n\n\n\n<p>As you may already know, most lazy-loaded images have a smooth transition when downloading the full-resolution image. We can implement one of these transitions by adding a few CSS classes and a CSS keyframe animation that plays whenever the image is revealed.<\/p>\n\n\n\n<p>The CSS for creating the blur effect is easy and can be accomplished by changing the filter property.<\/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-keyword\">static<\/span> styles = css`<span class=\"css\">\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-selector-pseudo\">:host<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">display<\/span>: inline-block;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-selector-class\">.cdl-image__img<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">display<\/span>: inline-block;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">max-width<\/span>: <span class=\"hljs-number\">100%<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-selector-class\">.cdl-image__img--hidden<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">opacity<\/span>: <span class=\"hljs-number\">0<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-selector-class\">.cdl-image__img--seen<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">animation<\/span>: blurTransition <span class=\"hljs-number\">0.2s<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-selector-class\">.cdl-image__img--lowres<\/span> {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">blur<\/span>(<span class=\"hljs-number\">6px<\/span>);\n\n\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">@keyframes<\/span> blurTransition {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a00% { <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">blur<\/span>(<span class=\"hljs-number\">6px<\/span>); }\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0100% { <span class=\"hljs-attribute\">filter<\/span>: <span class=\"hljs-built_in\">blur<\/span>(<span class=\"hljs-number\">0<\/span>); }\n\n\u00a0\u00a0\u00a0\u00a0}\n\n`<\/span>;<\/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>With that, the logic for changing the CSS classes dynamically can be done with a Lit directive known as <code>classMap<\/code>. This directive has an object passed into it that maps CSS classes with conditions that, if true, will apply the respective CSS classes.<\/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\">&lt;img\n\n\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span><\/span>=${classMap({\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-string\">\"cdl-image__img\"<\/span>: <span class=\"hljs-literal\">true<\/span>,\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-string\">\"cdl-image__img--lowres\"<\/span>: !<span class=\"hljs-keyword\">this<\/span>._imageLoaded,\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-string\">\"cdl-image__img--hidden\"<\/span>: src == <span class=\"hljs-literal\">null<\/span>,\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-string\">\"cdl-image__img--seen\"<\/span>: <span class=\"hljs-keyword\">this<\/span>._hasBeenInViewport &amp;&amp; <span class=\"hljs-keyword\">this<\/span>._imageLoaded,\n\n\u00a0\u00a0\u00a0\u00a0})}\n\n\u00a0\u00a0\u00a0\u00a0src=${src ? <span class=\"hljs-keyword\">this<\/span>._buildImageUrl() : nothing}\n\n\u00a0\u00a0\u00a0\u00a0alt=${<span class=\"hljs-keyword\">this<\/span>.alt ?? nothing}\n\n\u00a0\u00a0\u00a0\u00a0width=${<span class=\"hljs-keyword\">this<\/span>.width ?? nothing}\n\n\u00a0\u00a0\u00a0\u00a0height=${<span class=\"hljs-keyword\">this<\/span>.height ?? nothing}\n\n\u00a0\u00a0\u00a0\u00a0@load=${() =&gt; {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">if<\/span> (<span class=\"hljs-keyword\">this<\/span>._hasBeenInViewport) {\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<span class=\"hljs-keyword\">this<\/span>._imageLoaded = <span class=\"hljs-literal\">true<\/span>;\n\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}\n\n\u00a0\u00a0\u00a0\u00a0}}\n\n\/&gt;<\/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>The <code>cdl-image__img--hidden<\/code> class prevents the image from flicking the alt text before the lazy loaded thumbnail finishes downloading. The <code>cdl-image__img--lowres<\/code> class applies the blur only when the full images hasn\u2019t loaded yet, and finally the cdl-image__img&#8211;seen class applies when the image has both been fully loaded and scrolled into view, which is exactly when we want to remove the blur.<\/p>\n\n\n\n<p>With all of that you should have a functional Cloudinary image component that supports transformations and lazy loading! You can test this by adding enough HTML content to your example such that the image component goes off-screen, and scroll it into view. You can check the network tab in your browser\u2019s <code>devtools<\/code> to confirm that the larger image loads when you do this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Publishing<\/strong><\/h2>\n\n\n\n<p>When it comes to publishing your web component so that you can use it in your projects, I&#8217;d recommend following <a href=\"https:\/\/lit.dev\/docs\/tools\/publishing\/\">the Lit project\u2019s advice in their documentation<\/a>. To summarize a few major points made, keep it simple. Don\u2019t include polyfills, minify your components, and make sure to generate <code>sourcemaps<\/code> if you use <a href=\"https:\/\/www.typescriptlang.org\/\">TypeScript<\/a> so that consumers of the component get proper type completion. The project utilizing your components will already have its own build tooling and it will be a lot easier to use your component if you leave it mostly as-is when distributing it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Conclusion<\/strong><\/h2>\n\n\n\n<p>Web components are a great choice for distributing components that must be reusable and interoperable between multiple frameworks, and with libraries like Lit, they aren\u2019t difficult to write. From upload and transformation to optimization and delivery, the Cloudinary platform helps automate your entire visual asset lifecycle. <a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up for free<\/a> today.<\/p>\n\n\n\n<p>If you found this blog post helpful and want to discuss it in more detail, head over to the <a href=\"https:\/\/community.cloudinary.com\/\">Cloudinary Community forum<\/a> and its associated <a href=\"https:\/\/discord.com\/invite\/cloudinary\">Discord<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and interoperable custom elements that are encapsulated, so they\u2019re more easily integrated into other projects regardless of framework. Web components work with all major browsers, though with varying degrees of support. In this [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":36156,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[165,227],"class_list":["post-36154","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-image-transformation","tag-performance-optimization"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building an Optimized Cloudinary Image Custom Element With Web Components<\/title>\n<meta name=\"description\" content=\"Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and\" \/>\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\/optimized-image-custom-element-web-components\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building an Optimized Cloudinary Image Custom Element With Web Components\" \/>\n<meta property=\"og:description\" content=\"Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2024-10-23T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-10-23T18:20:59+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog-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\/optimized-image-custom-element-web-components#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Building an Optimized Cloudinary Image Custom Element With Web Components\",\"datePublished\":\"2024-10-23T14:00:00+00:00\",\"dateModified\":\"2024-10-23T18:20:59+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components\"},\"wordCount\":1143,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA\",\"keywords\":[\"Image Transformation\",\"Performance Optimization\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2024\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components\",\"url\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components\",\"name\":\"Building an Optimized Cloudinary Image Custom Element With Web Components\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA\",\"datePublished\":\"2024-10-23T14:00:00+00:00\",\"dateModified\":\"2024-10-23T18:20:59+00:00\",\"description\":\"Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building an Optimized Cloudinary Image Custom Element With Web Components\"}]},{\"@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":"Building an Optimized Cloudinary Image Custom Element With Web Components","description":"Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and","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\/optimized-image-custom-element-web-components","og_locale":"en_US","og_type":"article","og_title":"Building an Optimized Cloudinary Image Custom Element With Web Components","og_description":"Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and","og_url":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components","og_site_name":"Cloudinary Blog","article_published_time":"2024-10-23T14:00:00+00:00","article_modified_time":"2024-10-23T18:20:59+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog-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\/optimized-image-custom-element-web-components#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Building an Optimized Cloudinary Image Custom Element With Web Components","datePublished":"2024-10-23T14:00:00+00:00","dateModified":"2024-10-23T18:20:59+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components"},"wordCount":1143,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA","keywords":["Image Transformation","Performance Optimization"],"inLanguage":"en-US","copyrightYear":"2024","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components","url":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components","name":"Building an Optimized Cloudinary Image Custom Element With Web Components","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA","datePublished":"2024-10-23T14:00:00+00:00","dateModified":"2024-10-23T18:20:59+00:00","description":"Every developer knows the benefits of code reusability. It saves time and ensures consistency.. Web components (WC) allow you to create reusable and","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/optimized-image-custom-element-web-components#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Building an Optimized Cloudinary Image Custom Element With Web Components"}]},{"@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\/v1729104063\/optimized_Cloudinary_image_web_components-blog\/optimized_Cloudinary_image_web_components-blog.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36154","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=36154"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36154\/revisions"}],"predecessor-version":[{"id":36155,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/36154\/revisions\/36155"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/36156"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=36154"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=36154"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=36154"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}