Coded something up in Couch in an interesting way? Have a snippet or shortcode to share? Post it here for the community to benefit.
15 posts Page 1 of 2
Lazy loading images and other large files is a great way to get a big boost to page speed, while saving yourself and site visitors on unnecessary bandwidth usage. Lazy loading works by initially showing a 1-pixel image while hiding the real image source in a 'data-src' attribute. A script then loads the real source into the 'src' attribute only when it's needed by the viewport.
Code: Select all
<img src='data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' data-src='<cms:show my_image />' class='lazyload' alt='' />

It's a neat trick, but the non-standard markup makes it a hassle to write and manage the html code. That's where the <cms:lazy_load> tag steps in. This tag is a pre-processor that looks through your ordinary, standard markup and modifies image, iframe, audio, and video tags with the necessary changes.

I have chosen to use lazysizes.js ( with this module. It's a script that's free from dependencies like jQuery, and it has worked well for my own purposes. The necessary lazysizes scripts are bundled together with this tag, but will have to be added by you to the javascript on the page.

All lazy-load scripts work on the same principle, so this pre-processor can be made to work with other scripts. For images and iframes, the only difference should be the class name that triggers lazy loading (audio and video may be more complicated). This module uses "lazyload" as the trigger. You can configure the trigger class in your lazy-load script, or change the trigger definition in lazyload.php.
Code: Select all
define('LAZYLOAD_TRIGGER', 'lazyload');

Include the lazy load script(s) on the web page. The ls.unveilhooks.min.js script is a plugin for <audio> or <video> tags. It's not needed unless you're using it for those tags.
Code: Select all
<script src="ls.unveilhooks.min.js"></script>
<script src="lazysizes.min.js"></script>

Wrap any portion of your Couch code with the <cms:lazy_load> tag to pre-process tags for lazy loading. If you don't want a particular asset to be lazy-loaded, give it a class of 'eager' to skip over it.

By default, the tag processes all resource types. You can specify the types of content to target by adding parameters to the tag.
Code: Select all
<cms:lazy_load> //lazyloads images, iframes, audio/video
<cms:lazy_load 'image'> //only images
<cms:lazy_load 'iframe' 'video'> //only iframes and audio/video

Big galleries of images are the ideal use case for lazy loading.

Lazy loading works best when the <img> tag or css provides explicit sizes, so the screen can be blocked out correctly on loading. Otherwise, resizing and reflowing of the page content as new images load can give a janky experience. This issue can be challenging because it constrains your design choices.

With lazysizes.js, this module will also lazy-load responsive images.

This is my favorite use for lazy-loading. I consider it best practice to lazy load any embedded content like YouTube or Google Maps. They are always the biggest drag on page speed, both in analytics and user experience. Lazy loading embedded content makes a huge impact on the snappiness of a page, and this module makes it plug-and-play easy.

I've found that browsers can misbehave if an iframe's 'src' attribute isn't a url. This module uses an empty file named iframe-dummy.html in your site's root - the equivalent of a 1-pixel image. The <cms:lazy_load> tag will create the file in the site's root if it doesn't already exist.

Video and Audio Tags
The ls.unveilhooks plugin handles video and audio. It doesn't mess with a video or audio tag's sources. Instead, the pre-processor simply sets preload='none' to prevent downloading the audio or video file. The script changes the attribute to preload='auto' when it enters the viewport. It lazy loads the poster image just like other images.

CSS Hooks
The lazysizes script provides the class names 'lazyload,' 'lazyloading,' and 'lazyloaded' for you to hook styles to. The following CSS creates a fade-in effect for images and adds a loading gif to iframes and video.
Code: Select all
    /* Lazy Loading */
    .lazyload, .lazyloading {
        opacity: 0;
    .lazyloaded {
        opacity: 1;
        -webkit-transition: opacity 600ms;
        transition: opacity 600ms;
    iframe.lazyload, iframe.lazyloading, video.lazyload, video.lazyloading {
        opacity: 1;
        background: url('icons/loading.gif') no-repeat 50% 50%;

There is much more detailed information about using lazysizes.js at

To use the tag, unzip the attached folder into couch/addons/ and enable it in your couch/addons/kfunctions.php file:
Code: Select all
require_once( K_COUCH_DIR.'addons/lazyload/lazyload.php' ); ... r/lazyload


Great job, Tim :)
Thanks for this plugin. I was already using lazysizes, so it slipped in easily and works wonderfully.

I'm not having any problems with the plugin, but with an additions i've made and was wondering if you might be able to help.

I've modified the script slightly to add responsive bumf to image and am using getimagesize() for getting the information.
Code: Select all
//set up lazy load
//img src
if ( $node->getAttribute('src') ) {
    // GWIL -- get & set image size & create --aspect-ratio style
    $gwil_size = getimagesize($node->getAttribute('src'));

Has all been working fine, but have run into an issue when accessing a http protected site.
Now the getimagesize line is giving the following error:

Warning: getimagesize( ... 0x180.jpeg): failed to open stream: HTTP request failed! HTTP/1.1 401 Unauthorized in /homepages/37/d766678654/htdocs/dev/elfyn/dev1/c/addons/lazyload/lazyload.php on line 92

I've tried using a fixed url with the credentials baked in and that works, but just wondering if i'm missing something obvious.
Code: Select all
$gwil_size = getimagesize('');

I can figure out a hack using the above, and it's only the dev server that is password protected, but I would like to get it working eitherway.

I know next to nothing of php so would really appreciate any pointers.


ps sorry if i've posted in wrong palce
So here's something to try. Instead of using the url, use the file path when using getimagesize. That should work around the issue of needing http credentials.

Hope it helps!
Code: Select all
$gwil_url = $node->getAttribute('src');
$gwil_dir = str_replace(K_SITE_DIR, K_SITE_URL, $gwil_url);
$gwil_size = getimagesize($gwil_dir);

P.S. I'm glad the addon has been useful to you. If you have a useful mod or application for the script, I'd encourage you to share it here, so someone else may benefit.
Should be the other way around ;)
Code: Select all
$gwil_dir = str_replace( K_SITE_URL, K_SITE_DIR, $gwil_url);
@tim Thankyou so much, that works a treat; used mod by @trendoman
@trendoman Thankyou as well; didn't try wothout your mod, but it works perfectly with

thankyou both. the more i use couch the more i love it & the community is wonderfully supportive.

if anyone is interested the mod to lazyload.php does the following:

[edit] removed code as it was a dodgy first stab. new robust version posted below

Interesting, thanks!
@MiB your welcome.
i've nearly got a more complete version that takes care of placeholder images (converts them into base64 and inlines them) as well.
I'll post it when i'm happy with it
@MiB in case you're interested


i don't know if the following will be useful to anybody else, but it works well for me so am passing it on.

while building a site for an artist i needed a system to:
  1. lazyload
  2. auto create a responsive img which will grow upto max-size of img but can also be constrained horizontally and vertically whilst maintaning the original aspect ratio
  3. auto insert an original img sized placeholder with setable background (stop any and all page jumping)
  4. allow for an original img sized image placeholder (eg. dominant colour or lqip) to be inlined (so its there on page load) (i use a 5x? thumbnail blurred & clipped)
  5. allow for lazy unloading without page jumping

after much faffing around trying to find a solution that worked, i've come up with a variation on lazyload.php that:

  1. does what the original lazyload does (insert noscript; swap src->data-src & srcset->data-srcset; etc)
  2. adds a wrapper and puts original img in it (set max-width on wrapper of img width)
  3. adds an svg placeholder with dimensions of src img & preserveAspectRatio to the wrapper before the original image
  4. if data-ph is present on original img and contains a valid thumbnail filename, adds an extra img to the wrapper after the orignal img, with the thumbnail inlined as src=data-uri
  5. adds classes to original img and wrapper if src cannot be found (so can't determine img size)

with some css jiggerypokery this means:

  1. the svg placeholder ensures no page jumping while images are loading.
  2. the svg placeholder also works in sliders (i use tiny-slider with autoheight, so it knows what heights images are prior to them being lazyloaded)
  3. the data-ph[/] allows a [i]dominant colour or lqip image to be placed inline so that its is there on page load (i use a 5px wide thumbnail auto created with jcropthumb blurred, and then bring the lazyloaded img in from opacity:0 and blurred)
  4. when used with srcset allows optimal img size delivery based on device width (i auto-create different images with jcropthumb from an original uploaded (usually uncompressed) image at the sizes i need with variable compression)

so the following changes in lazyload.php:
Code: Select all
define('G_WRAP', 'rimg');
define('G_PH', 'rimg__ph');
define('G_IMG', 'rimg__img');
define('G_LQIP', 'rimg__lqip');
define('G_CLIP', 'rimg__clip');
define('G_DATA_PH', 'data-ph');
define('G_IMG_NO_SRC', 'rimg__img--no-src');
define('G_WRAP_NO_SRC', 'rimg--no-src');

Code: Select all
// GWIL - get src & data-src
$g_src = $node->getAttribute('src');
$g_data_src = $node->getAttribute('data-src');

//set up lazy load
//img src
// GWIL -- swap src/data-src if needed & get url for image src
if ($g_src) {
    $node->setAttribute('data-src', $g_src);
    $node->setAttribute('src', '');
    $g_url = $g_src;
}  elseif ($g_data_src) {
    $g_url = $g_data_src;

// GWIL - get relative path for src from url
$g_dir = str_replace(K_SITE_URL, K_SITE_DIR, $g_url);

// GWIL - if file exists && !0 size
if ( filesize($g_dir) > 0) {
    // GWIL - get dimensions
    $g_size = getimagesize($g_dir);
    $g_imgW = $g_size[0];
    $g_imgH = $g_size[1];

    // GWIL - create wrapper
    $g_wrap = $dom->createElement('div');
    $g_wrap->setAttribute('class', G_WRAP);
    $g_wrap->setAttribute('style', 'max-width:'.$g_imgW.'px');
    // ...insert before original node
    $node->parentNode->insertBefore($g_wrap, $node);

    // GWIl - create svg placeholder
    $g_svgPh = $dom->createElement('svg');
    $g_svgPh->setAttribute('class', G_PH);
    $g_svgPh->setAttribute('xmlns', '');
    $g_svgPh->setAttribute('preserveAspectRatio', 'xMidYMid meet');
    $g_svgPh->setAttribute('x', '0');
    $g_svgPh->setAttribute('y', '0');
    $g_svgPh->setAttribute('width', $g_imgW);
    $g_svgPh->setAttribute('height', $g_imgH);
    // ...append to wrapper

    // GWIL - set classes on original img
    $node->setAttribute('class', trim($node->getAttribute('class') . ' ' . G_IMG.' '.LAZYLOAD_TRIGGER));
    // GWIL - append original node to end of wrapper

    // GWIL - does it have data-placeholder attribute
    $g_hasPh = $node->hasAttribute(G_DATA_PH);
    // ...get placeholder if present
    $g_phPath = $node->getAttribute(G_DATA_PH);
    // ...get relative path for placeholder from url
    $g_phFile = str_replace(K_SITE_URL, K_SITE_DIR, $g_phPath);
    // ...if lqip exists && !0 size
    if ( ($g_hasPh) && (filesize($g_phFile))) {
        // ...create lqip
        $g_lqip = $dom->createElement('img');
        $g_lqip->setAttribute('class', G_LQIP);
        // ...get image & convert to base64
        $g_lqip64 = base64_encode(file_get_contents($g_phFile));
        // ...set up data uri
        $g_lqipUri = 'data:'.mime_content_type($g_phFile).';base64,'.$g_lqip64;
        $g_lqip->setAttribute('src', $g_lqipUri);
        // ...append lqip to end of wrapper
else {
    // ...src not found or zero size so add classes
    $node->setAttribute('class', trim($node->getAttribute('class') . ' ' . G_IMG_NO_SRC));

mean that:
Code: Select all
    <img class="c-gal__img some_other_class"
         src="<cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_full' />"
         srcset="<cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_1920' />       1920w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_1600' />       1600w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_1366' />       1366w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_1024' />       1024w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_960'  />        960w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_800'  />        800w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_640'  />        640w,
                      <cms:get_field masterpage='image_library.php' page=k_page_name 'r_img_320'  />        320w"

         data-ph="<cms:show r_img_ph />"

gets output as:
Code: Select all
<div class="rimg" style="max-width:1600px">
    <svg xmlns=""
         preserveAspectRatio="xMidYMid meet"
         x="0" y="0"
    <img class="c-gal__img rimg__img lazyloaded"
         data-srcset="http://.../image/9-1600x1121.jpg 1920w,
                      http://.../image/9-1600x1121.jpg 1600w,
                      http://.../image/9-1366x957.jpg  1366w,
                      http://.../image/9-1024x717.jpg  1024w,
                      http://.../image/9-960x672.jpg   960w,
                      http://.../image/9-800x560.jpg   800w,
                      http://.../image/9-640x448.jpg   640w,
                      http://.../image/9-320x224.jpg   320w"
         srcset="http://.../image/9-1600x1121.jpg 1920w,
                 http://.../image/9-1600x1121.jpg 1600w,
                 http://.../image/9-1366x957.jpg  1366w,
                 http://.../image/9-1024x717.jpg  1024w,
                 http://.../image/9-960x672.jpg   960w,
                 http://.../image/9-800x560.jpg   800w,
                 http://.../image/9-640x448.jpg   640w,
                 http://.../image/9-320x224.jpg   320w">
    <img class="rimg__lqip"

and with this css:
Code: Select all
/* warn the browser */
.lazyload {
    will-change: opacity, filter, -webkit-filter, -moz-filter, -o-filter, -ms-filter;
/* animations */
/* fade image in */
@keyframes ll-img-fade-in {
    from {
        opacity: 0;
        filter:         blur(55px);
        -webkit-filter: blur(55px);
        -moz-filter:    blur(55px);
        -o-filter:      blur(55px);
        -ms-filter:     blur(55px);
        filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='55'); /* IE lte 9 */
    to   {
        opacity: 1;
        filter: blur(0);
        -webkit-filter: blur(0);
        -moz-filter: blur(0);
        -o-filter: blur(0);
        -ms-filter: blur(0);
        filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='0'); /* IE lte 9 */
/* fade lqip out */
@keyframes ll-ph-fade-out {
    to { opacity: 0;}
/* responsive image container */
.rimg {
    position: relative;
    display: inline-block;

    /* clip scaled lqip */
    overflow: hidden;

    -webkit-box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), 0 0 0 0 rgba(0, 0, 0, 0), 00 0 0 rgba(0, 0, 0, 0);
    box-shadow: 0 0 0 0 rgba(0, 0, 0, 0), 0 0 0 0 rgba(0, 0, 0, 0), 00 0 0 rgba(0, 0, 0, 0);

    transition: box-shadow .5s cubic-bezier(0.0, 0.0, 0.2, 1), -webkit-box-shadow .5s cubic-bezier(0.0, 0.0, 0.2, 1);
    will-change: box-shadow, -webkit-box-shadow;
/* responsive image placeholder - shown by default, determines max-height */
.rimg__ph {
    position: relative;
    display: inline-block;
    max-height: calc(100vh - 16rem);
    max-width: 100%;

    width: auto;
    height: auto;

    background-color: ghostwhite;
/* responsive image image & lqip */
    position: absolute;
    display: block;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    width: auto;
/* respondive image image */
.rimg__img {
    opacity: 0;
.rimg__lqip {
    transform: scale(1.1);
    filter:         blur(15px);
    -webkit-filter: blur(15px);
    -moz-filter:    blur(15px);
    -o-filter:      blur(15px);
    -ms-filter:     blur(15px);
    filter:progid:DXImageTransform.Microsoft.Blur(PixelRadius='15'); /* IE lte 9 */
/* responsive image image - fade in once loaded */
.rimg__img.lazyloaded {
    animation-fill-mode: forwards;
    animation: ll-img-fade-in 200ms cubic-bezier(0.4, 0.0, .2, 1) forwards;
/* responsive image lqip - hide if main image lazy loads */
.rimg__img.lazyloaded ~ .rimg__lqip {
    animation-fill-mode: forwards;
    animation: ll-ph-fade-out 1000ms linear forwards;

everything comes together.
I'm currently testing this as much as possible, but it seems robust. will post any changes.
[edit] - changed order of lqip & img to allow lqip to be removed after lazyloading
15 posts Page 1 of 2