<main>
<article>
<h1>progressive-image.js</h1>
<p><a href="https://github.com/craigbuckler/progressive-image.js"><strong>GitHub</strong></a> | <a href="https://www.npmjs.com/package/progressive-image.js"><strong>npm</strong></a> | <a href="https://gum.co/vIjey"><strong>donate</strong></a> | <a href="https://twitter.com/craigbuckler">@craigbuckler</a> | <a href="https://craigbuckler.com/">craigbuckler.com</a></p>
<p><a href="https://github.com/craigbuckler/progressive-image.js"><code>progressive-image.js</code></a> implements a progressively-loaded image effect similar to those seen on <a href="https://code.facebook.com/posts/991252547593574/the-technology-behind-preview-photos/">Facebook</a> and <a href="https://jmperezperez.com/medium-image-progressive-loading-placeholder/">Medium</a>. A very small blurred image is replaced with the full-resolution equivalent when the element is scrolled into view. See <a href="https://www.sitepoint.com/how-to-build-your-own-progressive-image-loader/">How to Build Your Own Progressive Image Loader</a> for information about how it was initially developed.</p>
<p>Please use the code as you wish. <a href="https://twitter.com/craigbuckler">Tweet me @craigbuckler</a> if you find it useful and consider <a href="https://gum.co/vIjey">donating toward development</a>.</p>
<a href="https://res.cloudinary.com/oworks/image/upload/f_auto/v1593762372/pimage/iceland" data-srcset="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_400/v1593762372/pimage/iceland 400w, https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_800/v1593762372/pimage/iceland 800w, https://res.cloudinary.com/oworks/image/upload/f_auto/v1593762372/pimage/iceland 1600w" class="primary progressive replace">
<img src="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_32/v1593762372/pimage/iceland" class="preview" loading="lazy" width="32" height="8" alt="Iceland" />
</a>
<h2 id="contents">Contents</h2>
<ol>
<li><a href="#benefits">benefits</a></li>
<li><a href="#basic">basic example</a></li>
<li><a href="#native">native lazy-loading</a></li>
<li><a href="#link">retain the link</a></li>
<li><a href="#container">alternative container elements</a></li>
<li><a href="#responsive">responsive images (<code>srcset</code> and <code>sizes</code>)</a></li>
<li><a href="#reveal">custom reveal effects</a></li>
<li><a href="#background">CSS background images</a></li>
<li><a href="#notes">usage notes</a></li>
</ol>
<h2 id="benefits">Benefits</h2>
<a href="https://res.cloudinary.com/oworks/image/upload/f_auto/v1593769674/pimage/jump" class="boxout2 progressive replace">
<img src="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_20/v1593769674/pimage/jump" class="preview" loading="lazy" width="20" height="15" alt="jump" />
</a>
<ul>
<li>saves unnecessary bandwidth</li>
<li>fast loading, high performance, images loaded on view</li>
<li>supports any image type (JPEG photographs are most appropriate)</li>
<li>supports <a href="#responsive">responsive images</a> (<code>srcset</code> and <code>sizes</code> attributes)</li>
<li>supports <a href="#background">CSS background images</a></li>
<li>supports <a href="#native">native lazy loading and aspect ratios</a></li>
<li>lightweight: 1,407 bytes of JavaScript, 407 bytes of optional CSS (minified)</li>
<li>any <a href="#reveal">CSS reveal effect</a> can be applied</li>
<li>no external dependencies – works with any framework</li>
<li>makes up to three attempts if images fail to download</li>
<li>works in all modern browsers (IE10+)</li>
<li>progressively-enhanced to work in older browsers</li>
<li>easy to use</li>
</ul>
<a href="https://res.cloudinary.com/oworks/image/upload/f_auto/v1593770219/pimage/canoe" class="boxout1 progressive replace">
<img src="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_20/v1593770219/pimage/canoe" class="preview" loading="lazy" width="20" height="15" alt="canoe" />
</a>
<p>The preview image can be very small – perhaps 20px in width and saved with high JPEG compression. This typically results in an image less than 500 bytes in size. It be added to the page directly or inlined as a base-64 data URI.</p>
<p>The large image can be any size but it <strong>must match the preview image's aspect ratio</strong>. For example, a preview image of 20x15 could be used for large image sizes including 200x150, 400x300, 1600x1200, etc.</p>
<p><em>The page layout will re-layout and jump if differing aspect ratios are used.</em></p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="basic">Basic example</h2>
<p>The page must load the CSS and JavaScript. It can be placed anywhere on the page but as early as possible in <code><head></code> is ideal:</p>
<pre><code><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/progressive-image.js/dist/progressive-image.css">
<script src="https://cdn.jsdelivr.net/npm/progressive-image.js/dist/progressive-image.js" defer></script></code></pre>
<p>CDN URLs are shown above but you can also use <a href="https://www.npmjs.com/package/progressive-image.js"><code>npm</code></a> and a bundler:</p>
<pre><code>npm i progressive-image.js</code></pre>
<p>To use the lazy loading effect:
<ol>
<li>add an <code><img></code> tag for the <strong>small image</strong> with a class of <code>preview</code></li>
<li>surround it with <code><a></code> link to the <strong>large image</strong> and add both <code>progressive replace</code> as classes</li>
</ol>
<p>Example:</p>
<pre><code><a href="full.jpg" class="progressive replace">
<img src="tiny.jpg" class="preview" alt="image" />
</a></code></pre>
<p><strong>The preview and full-size images must have the same aspect ratio</strong>, e.g. 40x30 and 800x600.</p>
<p>The full image is revealed when the preview is scrolled into view. CSS3 effects are used to fade and zoom the image:</p>
<a href="https://res.cloudinary.com/oworks/image/upload/f_auto/v1593771256/pimage/squirrel" class="full progressive replace">
<img src="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_20/v1593771256/pimage/squirrel" class="preview" loading="lazy" width="20" height="15" alt="squirrel" />
</a>
<p>After replacement, link-clicking is disabled and the HTML becomes:</p>
<pre><code><a href="full.jpg" class="progressive">
<img src="full.jpg" alt="image" />
</a></code></pre>
<p>Any <code>class</code> names applied to the preview image are applied to the full image (except <code>preview</code>).</p>
<p>If JavaScript or image loading fails, a blurred version of the preview image can be clicked to view the full image:</p>
<a href="https://res.cloudinary.com/oworks/image/upload/f_auto/v1593771256/pimage/squirrel" class="full progressive">
<img src="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_20/v1593771256/pimage/squirrel" class="preview" loading="lazy" width="20" height="15" alt="squirrel" />
</a>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="native">Native lazy-loading</h2>
<p>To improve performance, add <code>width</code>, <code>height</code>, and <code>loading="lazy"</code> attributes to the <strong>preview image</strong>:</p>
<pre><code><img src="tiny.jpg" class="preview"
width="20" height="15"
loading="lazy"
alt="image"
/></code></pre>
<p>Modern browsers will set the correct aspect ratio and load the preview image shortly before it is scrolled into view.</p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="link">Retain the link</h2>
<p>If you require a responsive image to remain a real link, set the <code>href</code> to the link address and add a <code>data-href</code> attribute with the large image URL:</p>
<pre><code><a href="http://site.com/"
data-href="full.jpg"
class="progressive replace">
<img src="tiny.jpg" class="preview" alt="image" />
</a></code></pre>
<p><em>Users will not be able to view the full image if downloading or JavaScript fails.</em></p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="container">Alternative container elements</h2>
<p>If necessary, most HTML container elements can be used with a <code>data-href</code> attribute rather than a link, e.g.</p>
<pre><code><figure data-href="full.jpg" class="progressive replace">
<img src="tiny.jpg" class="preview" alt="image" />
</figure></code></pre>
<p>Exceptions include <code><picture></code>, <code><video></code>, <code><audio></code>, and <code><iframe></code>. These are inappropriate and may cause unexpected browser behavior.</p>
<p><em>Users will not be able to view the full image if downloading or JavaScript fails.</em></p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="responsive">Responsive images</h2>
<p><a href="https://www.sitepoint.com/how-to-build-responsive-images-with-srcset/">Responsive images</a> of differing sizes can be defined in the container link/element using <code>data-srcset</code> and <code>data-sizes</code> attributes. These correspond to the standard <code>srcset</code> and <code>sizes</code> attributes, e.g.</p>
<pre><code><a href="small.jpg"
data-srcset="small.jpg 800w, large.jpg 1200w"
data-sizes="100vw"
class="progressive replace">
<img src="preview.jpg" class="preview" alt="image" />
</a></code></pre>
<p><em>(carriage returns added to aid legibility)</em></p>
<p>On replacement, the image code is transformed to:</p>
<pre><code><img src="small.jpg"
srcset="small.jpg 800w, large.jpg 1200w"
sizes="100vw"
alt="image" /></code></pre>
<p>Modern browsers will load large.jpg when the viewport width is 800px or greater.</p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="reveal">Custom reveal effects</h2>
<p>A class of <code>reveal</code> is applied to the full image when it is placed on the page. This starts a 1 second CSS3 animation named <code>progressiveReveal</code>:</p>
<pre><code>.progressive img.reveal {
animation: progressiveReveal 1s linear;
}
@keyframes progressiveReveal {
0% { transform: scale(1.05); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}</code></pre>
<p>This code can be changed or overridden with alternative animations, perhaps by applying another class to the container element.</p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="background">CSS background images</h2>
<p><code>progressive-image.js</code> can lazy-load CSS background images.</p>
<p>However, it is not best-suited to that task and CSS3 animations cannot be applied. You will get better results using a real <code><img></code> rendered below other content and maximized with <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit"><code>object-fit: cover</code></a>.</p>
<div class="full bgimg progressive replace">CSS background image</div>
<p>Example HTML:</p>
<pre><code><div class="bgimg progressive replace">content</div></code></pre>
<p>Any CSS selector can be used – a <code>bgimg</code> class has been added here. Set the full-size image in your CSS as normal, e.g.</p>
<pre><code>.bgimg {
background: url(large.jpg) 50% 50% no-repeat;
background-size: cover;
}</code></pre>
<p>then add a <code>.replace</code> definition with the smaller preview image:</p>
<pre><code>.bgimg.replace {
background-img: url(tiny.jpg);
}</code></pre>
<p><code>tiny.jpg</code> is replaced with <code>large.jpg</code> as soon as the element appears in the viewport. If necessary, media queries can be used to load appropriately-sized images.</p>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2 id="notes">Usage notes</h2>
<p>Thanks for trying <a href="https://github.com/craigbuckler/progressive-image.js/"><code>progressive-image.js</code></a> and please <a href="https://github.com/craigbuckler/progressive-image.js/issues/">report any issues</a> you encounter.</p>
<ol>
<li>The code works in all browsers from IE10 and above. IE10/11 will not smoothly blur the preview image. Older browsers fallback to click-to-view.</li>
<li>The code cannot be used for <a href="https://developer.mozilla.org/Web/HTML/Element/picture">art direction</a> where different images with different aspect ratios are requested according to the dimensions and/or orientation of a device using <code><picture></code> and <code><source></code> elements.</li>
<li>Only vertical scrolling is checked. All images in the horizontal plane will be loaded – it may not be suitable for carousels.</li>
<li>Progressive images dynamically added to the page using JavaScript are handled if a navigation event occurs or the browser supports <code>MutationObserver</code>.</li>
<li>Actual or perceived performance may be improved using an SVG preview image or <a href="https://css-tricks.com/data-uris/">inlined data URIs</a>.</li>
</ol>
<p class="top"><a href="#contents">back to contents…</a></p>
<h2>Image credit</h2>
<p>Images in this demonstration have been sourced from <a href="https://unsplash.com/">unsplash.com</a> and are credited to Matt Palmer, Joshua Gaunt, Viktor Jakovlev, Caleb Martin, & Red Zepplin.</p>
<a href="https://res.cloudinary.com/oworks/image/upload/f_auto/v1593773543/pimage/exmouth" data-srcset="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_400/v1593773543/pimage/exmouth 400w, https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_800/v1593773543/pimage/exmouth 800w, https://res.cloudinary.com/oworks/image/upload/f_auto/v1593773543/pimage/exmouth 1600w" class="primary progressive replace">
<img src="https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_32/v1593773543/pimage/exmouth" class="preview" loading="lazy" width="32" height="8" alt="Exmouth" />
</a>
</article>
</main>
/* main styles */
*, *:before, *:after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
scroll-behavior: smooth;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
font-size: 120%;
line-height: 1.4;
color: #444;
background-color: #fff;
overflow-x: hidden;
overflow-y: scroll;
}
article {
max-width: 42em;
padding: 0 4em;
margin: 0 auto;
}
h1, h2 {
margin-top: 1rem;
font-weight: normal;
}
h2 {
margin: 2em 0 0.2em;
}
p, a {
margin-top: 1em;
}
h2 + p {
margin-top: 0;
}
ul, ol {
margin: 1em 0 0 2em;
}
li {
margin-top: 0.8em;
}
pre {
white-space: pre;
padding: 2px 6px;
margin-top: 1.5em;
background-color: #eee;
border: 1px solid #999;
border-radius: 3px;
overflow: auto;
}
code {
font-family: monospace;
font-size: 1em;
padding: 3px;
border-radius: 3px;
background-color: #eee;
}
pre code {
padding: 0;
border-radius: 0;
background-color: inherit;
}
img {
max-width: 100%;
}
.top {
font-size: 0.85em;
text-align: right;
font-style: italic;
}
.bgimg {
background: url(https://res.cloudinary.com/oworks/image/upload/f_auto/v1593771256/pimage/squirrel) 50% 50% no-repeat;
background-size: cover;
height: 400px;
margin-top: 0.5em;
font-size: 2em;
text-align: center;
color: #fff;
text-shadow: 0 0 2px #000;
}
.bgimg.replace {
background-image: url(https://res.cloudinary.com/oworks/image/upload/f_auto,c_scale,w_20/v1593771256/pimage/squirrel);
}
.primary {
width: 100vw;
left: 50%;
margin-left: -50vw;
}
.full, .boxout1, .boxout2 {
clear: both;
width: 100%;
box-shadow: 0 3px 4px #000;
}
@media (min-width: 40em) {
.boxout1, .boxout2 {
clear: both;
float: left;
width: 22em;
margin: 1em 1em 1em -4em;
}
.boxout2 {
float: right;
margin: 1em -4em 1em 1em;
}
}