Lazy load your images and iframes

lazyload images and iframes

Lazy loading is an effective way to improve your frontend performance. And that’s especially important on eCommerce websites. In this article, you will read (and learn) how to reduce page load time by loading your

  1. images on scroll and
  2. iframes on demand.

Let’s get started with lazy load!

Feature request

Imagine a casual office (or, nowadays, Slack) conversation between a Marketing Specialists (MS) and you, a Developer (D).

  • MS: Listen, we have finished our marketing research and as a result, we have created some cool videos about our product line that should be shown before the footer on the homepage of our site! We would like to have a static image displayed for each video and once the user clicks on the image, the video starts playing.
  • D: Sure, no problem! Have the videos been uploaded to the official YouTube channel?
  • MS: They sure have! They got approved yesterday by the CEO, we are good to go!
  • D: Excellent! When you get the time, send me over the links for the videos and I will take over and start with the implementation.
  • MS: That’s great, thank you very much! I’ll email you some images for the videos as well.
  • D: There is no need for images, YouTube links will do.
  • MS: Really? Why is that?
  • D: Because YouTube provides you with a screenshot of your video by default.
  • MS: Really? That is awesome!
  • D: YouTube is awesome!
  • MS: Excellent! I’ll send you the links as soon as I get back to my computer. You know what? You are awesome! Talk to you later!

And the MS walks away… After a couple of minutes of thinking this through, I open my IDE and stretch and pop my fingers. And neck.

Planning and rollout

First of all, I need to select a layout handle and where to display the videos. The MS said:

…we would like these to be shown before the footer on the homepage…

So, cms_index_index.xml in Magento_Cms it is. Also, “after” attribute will have to be used.

app/design/frontend/Vendor/Theme/Magento_Cms/layout/cms_index_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="content">
            <block name="videos"
                   class="Magento\Framework\View\Element\Template"
                   template="Magento_Cms::videos.phtml"
                   after="-" />
        </referenceContainer>
    </body>
</page>

Next thing I need is the “videos.phtml” template file. :start_typing_on_the keyboard: :fetch_some_youtube_videos_from_history_list: :take_out_the_ids:

Et voilà!

app/design/frontend/Vendor/Theme/Magento_Cms/templates/videos.phtml

<?php $youtube_ids = ["ccnwzScp6bM","n2HgXRNVT7g","ux8GZAtCN-M","mtk5Ej-xLsM"]; ?>
<ul class="featured-videos">
    <?php foreach ($youtube_ids as $id): ?>
        <li class="featured-video">
            <div class="iframe-container iframe-container--56">
                <img class="poster"
                     src="https://img.youtube.com/vi/<?= $block->escapeHtml($id) ?>/hqdefault.jpg"
                     alt="<?= $block->escapeHtml(__('Youtube video')) ?>" />
                <iframe src="https://www.youtube.com/embed/<?= $block->escapeHtml($id) ?>"
                        frameborder="0"
                        allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
                        allowfullscreen></iframe>
            </div>
        </li>
    <?php endforeach ?>
</ul>

And to make things look nice and according to the style guide…

app/design/frontend/Vendor/Theme/Magento_Cms/web/css/source/_extend.less

@import './_videos';

app/design/frontend/Vendor/Theme/Magento_Cms/web/css/source/_videos.less

& when (@media-common = true) {
    .featured-videos {
        margin: 0;
        padding: 0;
        list-style: none;
        display: flex;
        justify-content: space-between;
    }
 
    .featured-video {
        flex: 0 0 24%;
    }
 
    .iframe-container {
        position: relative;
        height: 0;
        overflow: hidden;
 
        &:before {
            content: '▶';
            display: block;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: black;
            border-radius: 50%;
            border: 2px solid white;
            padding: 8px;
            z-index: 2;
            width: 20px;
            height: 20px;
            color: white;
            text-align: center;
        }
    }
 
    .iframe-container--56 {
        padding-top: 56.25% !important;
    }
 
    .iframe-element {
        position: absolute;
        width: 100%;
    }
 
    .poster {
        cursor: pointer;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%) scale(1);
        transition: transform .3s linear;
        max-height: none;
        opacity: 1;
 
        &:hover {
            transform: translate(-50%, -50%) scale(1.05);
            transition: transform .3s linear;
        }
    }
 
    .iframe-container.clicked {
        &:before,
        &:after {
            content: none;
        }
 
        .poster {
            opacity: 0;
            transition: opacity 1s linear .3s;
        }
    }
 
    .iframe {
        top: 0;
        opacity: 0;
        visibility: hidden;
        transition: opacity .3s linear 1s;
        height: 100%;
 
        &.loaded {
            opacity: 1;
            visibility: visible;
        }
    }
}

It’s time to see what it looks like in the browser!

Great! It was also said:

once the user clicks on the image, the video starts playing.

OK, so hide the iframe and show it after a user clicks on it. Easy peasy! In order to do that, I need to create a JS component, which must be invoked from the template file.

app/design/frontend/Vendor/Theme/Magento_Cms/templates/videos.phtml

<?php $youtube_ids = ["ccnwzScp6bM","n2HgXRNVT7g","ux8GZAtCN-M","mtk5Ej-xLsM"]; ?>
<ul class="featured-videos">
    ...
</ul>
 
<!-- added the code below -->
<script type="text/x-magento-init">
    {
        ".featured-videos": {
            "toggle-img-iframe": {}
        }
    }
</script>

Finally, I can write some JavaScript code….

app/design/frontend/Vendor/Theme/Magento_Cms/requirejs-config.js

var config = {
    "map": {
        "*": {
            "toggle-img-iframe": "Magento_Cms/js/toggle-img-iframe"
        }
    }
};

app/design/frontend/Vendor/Theme/Magento_Cms/web/js/toggle-img-iframe.js

define(['jquery'], function ($) {
    'use strict';
 
    $.widget('inchoo.toggleImgIframe', {
        _init: function () {
            $(this.element).find('.iframe-container').each(function (i, img) {
                $(this).on('click', function () {
                    $(this).addClass('clicked');
                });
            });
        }
    });
 
    return $.inchoo.toggleImgIframe;
});

Looks like everything is okay, IDE is not throwing errors to me… I’ll open the browser again and just click on the third item to see if it works…

And it works! Sweet! Just to be sure, I’ll open the Networks tab and see how it affects the performance.

via GIPHY

Time to optimize

Whoooooops! These four videos bring an extra 132 kB on page load (33.3 kB combined on average per each video) – DOMLoaded is 1.05s, page load is finished in 2.96s! This new feature has a negative impact on the page load time indicators! I must do something in order to improve the page performance…

Luckily, I have just the tool for this! I will include the lazyloading script by aFarkas, that should take care of my performance problem!

OK, so this is a pure JavaScript plugin, I can include it as a standalone library. The best way would be to place it on the theme level.

app/design/frontend/Vendor/Theme/requirejs-config.js

var config = {
    "deps": [
        "js/lazysizes.min"
    ]
};

According to documentation, I have to apply the following changes to any img elements that are used:

  1. rename src attribute to data-src attribute
  2. add lazyload CSS class

But, hey! If the path to the actual image must be defined as data-src attribute, the img element will be invalid! What now?

:starts_researching_on_the_Internet:

Bingo! I will use the smallest possible GIF (only 26 B, I kid you not!) found on this blog post. It will be used as the default image on page load, so everything will work smoothly!

app/design/frontend/Vendor/Theme/Magento_Cms/templates/youtube.phtml

<?php 
...
 
// changes on <img />
// 1 - added lazyload class
// 2 - renamed src -> data-src
// 3 - added new src attribute and its associated value: src="<?= $block->getViewFileUrl('images/smallest.gif') ?>"
 
                <img class="iframe-element poster lazyload"
                     src="<?= $block->getViewFileUrl('images/smallest.gif') ?>"
                     data-src="https://img.youtube.com/vi/<?= $block->escapeHtml($id) ?>/hqdefault.jpg"
                     alt="<?= $block->escapeHtml(__('Youtube video')) ?>" />

Now let’s check the results in the browser.

The final touches

Wonderful! And now to finish the last part for this feature…

…static image displayed and once the user clicks on the image, the video starts playing.

app/design/frontend/Vendor/Theme/Magento_Cms/templates/youtube.phtml

<?php 
...
 
// changes on <iframe>
// 1 - renamed src -> data-src
// 2 - added new src attribute and its associated value: src="<?= $block->getViewFileUrl('images/smallest.gif') ?>"
 
                <iframe class="iframe-element iframe"
                    src="<?= $block->getViewFileUrl('images/smallest.gif') ?>"
                    data-src="https://www.youtube.com/embed/<?= $block->escapeHtml($id) ?>"
                    frameborder="0"
                    allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
                    allowfullscreen></iframe>
 
...

app/design/frontend/Inchoo/Blog/Magento_Cms/web/js/toggle-img-iframe.js

...
                $(this).on('click', function () {
                    $(this).addClass('clicked');
 
// changes
// 1 - get the iframe element in DOM
// 2 - get the actual URL of the video from the data-src attribute
// 3 - append "?autoplay=1" string to the actual URL of the video
// 4 - show the iframe by adding the "loaded" CSS class 
 
                    var iframe = $(this).children('.iframe');
                    var iframeSrc = iframe.data('src');
 
                    iframe
                        .attr('src', iframeSrc + '?autoplay=1')
                        .addClass('loaded');
                });
...

Let’s check the browser one more time to see if it really works.

And the Networks tab to see the final results….

Awesome! The videos are here, all requirements are fulfilled, let me just compare the page load times.

  • DOM ready – from 1.05s to .75s (drop by almost 29%)
  • page load time – from 2.96s to 1.76s (drop by almost 41%)

:checking_my_inbox:

Oh, great, the YouTube links are here!. Let me take those IDs and put them in the file… Okay… Now the browser…

Oh, the videos are here, awesome! I’ll just reply back and let the MS know that the videos are ready to be previewed and/or deployed to live site.

Was this helpful?

I wanted to share a real-life example of my process and how I went from feature request through planning, testing, and performance optimization. I hope you found this useful and that you’ll adopt some of these techniques in your daily work.

And if you need some assistance around performance optimization on your projects, feel free to contact us using the form below!

Related Inchoo Services

You made it all the way down here so you must have enjoyed this post! You may also like:

Mitigating Facebook’s “x-fb-http-engine: Liger” site hammering using Apache or nginx Drazen Karacic-Soljic
, | 9

Mitigating Facebook’s “x-fb-http-engine: Liger” site hammering using Apache or nginx

Is your website being hammered by Facebook and what liger has to do with it? Drazen Karacic-Soljic
, | 22

Is your website being hammered by Facebook and what liger has to do with it?

Free your cart, and the speed will follow Ivan Curdinjakovic
Ivan Curdinjakovic, | 0

Free your cart, and the speed will follow

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.

Tell us about your project

Drop us a line. We'd love to know more about your project.