Medium like image loading in hugo

Aug 31, 2019 · 5 min read

   hugo

Quick walkthrough of strategy

  • Initially when page is loading browser request image as well(provided in src tag)

    • We will provide only 2px image placeholder of original image
    • using css will make medium like effect on image
    • using js, when original image arrives after t ms will replace placeholder image with original one.

Quick guide

I assume you’re using a hugo theme and you want to add this feature in the theme. Even if it’s not a theme it will still work just you need to keep files in layouts/** directory rather than themes/THEME/layouts/**

create shortcode inside themes/THEME/layouts /shortcodes/SHORTCODENAME .html

In my case SHORTCODENAME is lazyImage

{{ $image := (.Page.Resources.GetMatch  ( .Get "src")) }}
{{ $placeholder := $image.Resize "2x q20" }}
{{ $width := or ( .Get "width") (printf "%dpx" ($image.Width)) }}
{{ $height := or ( .Get "height") (printf "%dpx" ($image.Height)) }}

<div class="image_placeholder" style="max-width: {{$width}} ; max-heigth: {{$height}}">
<div class="image_placeholder_inside">
  <img src="{{ $placeholder.Permalink }}" data-src="{{ $image.Permalink }}" class="img-small lazy">
  <div style="padding-bottom: {{ div (mul $image.Height 100.0) $image.Width }}%;"></div>
</div>
</div>

js

this code should run, so place it in any js file you want!

document.addEventListener("DOMContentLoaded",function(){
    /**  lazy image loading  */

    var lazyloadImages;

    if ("IntersectionObserver" in window) {
        lazyloadImages = document.querySelectorAll(".lazy");
        var imageObserver = new IntersectionObserver(function (entries, observer) {

            entries.forEach(function (entry) {


                let image = entry.target;
                var smallImg = new Image();
                smallImg.src = image.src;
                smallImg.onload = function () {
                    image.classList.add('loaded');
                };

                if (entry.isIntersecting) {

                    let ogImage = new Image();

                    ogImage.src = image.dataset.src;
                    ogImage.onload = function(){
                        ogImage.classList.add("loaded")
                    }

                    image.parentNode.appendChild(ogImage)
                    image.classList.remove("lazy");
                    imageObserver.unobserve(image);
                }
            });
        });

        lazyloadImages.forEach(function (image) {
            imageObserver.observe(image);
        });
    } else {
        // fall back to older browsers
        let lazyloadThrottleTimeout;

        lazyloadImages = e(".lazy");
        function lazyload() {
            if (lazyloadThrottleTimeout) {
                clearTimeout(lazyloadThrottleTimeout);
            }

            lazyloadThrottleTimeout = setTimeout(function () {
                let scrollTop = window.pageYOffset;

                lazyloadImages.each(function(index,img){
                    if(!img)
                        return;

                    let smallImg = new Image();
                    smallImg.src = img.src;
                    smallImg.onload = function () {
                        img.classList.add('loaded');
                    };

                    if (e(img).offset().top < (window.innerHeight + scrollTop)) {

                        let ogImage = new Image();

                        ogImage.src = img.dataset.src;
                        ogImage.onload = function () {
                            ogImage.classList.add("loaded")
                        }

                        img.parentNode.appendChild(ogImage);
                        img.classList.remove('lazy');
                        lazyloadImages.splice(index, 1)
                    }



                });

                if (lazyloadImages.length == 0) {
                    document.removeEventListener("scroll", lazyload);
                    window.removeEventListener("resize", lazyload);
                    window.removeEventListener("orientationChange", lazyload);
                }
            }, 20);
        }
        document.addEventListener("scroll", lazyload);
        window.addEventListener("resize", lazyload);
        window.addEventListener("orientationChange", lazyload);
        lazyload();
    }

})

css

similar to js put wherever you like…

.image_placeholder_inside {
  background-color: #f6f6f6;
  background-size: cover;
  background-repeat: no-repeat;
  position: relative;
  overflow: hidden;
}

.image_placeholder_inside img {
  position: absolute;
  opacity: 0;
  top: 0;
  left: 0;
  width: 100%;
  transition: opacity 1s linear;
}

.img-small {
  filter: blur(50px);
  /* this is needed so Safari keeps sharp edges */
  transform: scale(1);
}

.image_placeholder_inside img.loaded {
  opacity: 1;
}

.image_placeholder{
  display: block;
}

Usage

now in content/**/**.md

parameterrequireddefaultvalue
srcyesNArelative image path
widthnodefault image widthvalid html width :- 50px,20rem,5%
heightnodefault image heightvalid html height :- 50px,20rem,5%

{{ < lazyImage src="images/someimage.jpg” > }}

{{ < lazyImage width="30rem” height="10rem” src="images/someimage.jpg” > }}

Or

{{ % lazyImage src="images/someimage.jpg” % }}

{{ % lazyImage width="30rem” height="10rem” src="images/someimage.jpg” % }}


Note

  • This doesn't include images in /static/img/** folder
  • Image should be at same/below level w.r.t markup file

Eg.

- content/blog
    | 
	|- Image-loading-like-medium
	|	|
	|	- index.md  <--- you'll be using the shortcode here
	|
	|
	|- images
		|
		- someimage.jpg

Demo

Below image is ~ 400 KB, reload the page(shift + f5 for hard reload) to see the effect

reference(s)

For any queries, Do let me know in comments down below.

Thankyou, peace out 😄


comments powered by Disqus