Encountering content jumps may be very frustrating. Especially if your scroll event is being tracked and the size of your Document doesn’t get updated after an element got loaded into the DOM.
It’s pretty easy to handle images in your template and manipulate them to behave as expected. But what to do with the WYSIWYG images?
<div class="lazy-container" style="padding-bottom: image_ratio%;">
<img data-src="image_path" />
</div>
The tricks are:
This way we assure the wrapper inherits images height although the image is not in the DOM yet. Obviously we need the height and the width of the image, but wordpress delivers this data. We will take care of that later.
.lazy-container {
position: relative;
}
.lazy-container img {
position:absolute;
top:0;
left:0;
max-width: 100%;
}
.lazy-container:after {
background-color: #eee;
content: "";
display: block;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background-image: url("data:image/svg+xml;base64,PCEtLSBCeSBTYW0gSGVyYmVydCAoQHNoZXJiKSwgZm9yIGV2ZXJ5b25lLiBNb3JlIEAgaHR0cDovL2dvby5nbC83QUp6YkwgLS0+Cjxzdmcgd2lkdGg9IjM4IiBoZWlnaHQ9IjM4IiB2aWV3Qm94PSIwIDAgMzggMzgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjYWFhIj4KICAgIDxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMSAxKSIgc3Ryb2tlLXdpZHRoPSIyIj4KICAgICAgICAgICAgPGNpcmNsZSBzdHJva2Utb3BhY2l0eT0iLjUiIGN4PSIxOCIgY3k9IjE4IiByPSIxOCIvPgogICAgICAgICAgICA8cGF0aCBkPSJNMzYgMThjMC05Ljk0LTguMDYtMTgtMTgtMTgiPgogICAgICAgICAgICAgICAgPGFuaW1hdGVUcmFuc2Zvcm0KICAgICAgICAgICAgICAgICAgICBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iCiAgICAgICAgICAgICAgICAgICAgdHlwZT0icm90YXRlIgogICAgICAgICAgICAgICAgICAgIGZyb209IjAgMTggMTgiCiAgICAgICAgICAgICAgICAgICAgdG89IjM2MCAxOCAxOCIKICAgICAgICAgICAgICAgICAgICBkdXI9IjFzIgogICAgICAgICAgICAgICAgICAgIHJlcGVhdENvdW50PSJpbmRlZmluaXRlIi8+CiAgICAgICAgICAgIDwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==");
background-repeat: no-repeat;
background-position: center center;
background-size: 10%;
transition: all 0.3s;
}
We use a pseudo selector to style the placeholder in order to make an animation possible.
import "waypoints/lib/noframework.waypoints";
const images = document.querySelectorAll(".lazy");
images.forEach((image) => {
new Waypoint({
element: image,
handler: function (direction) {
handleLazyItem(image);
this.destroy(); // let's get rid of the waypoint after the image got loaded
},
offset: "50%"
});
});
const handleLazyItem = (image) => {
// handle the src attribute
const src = image.getAttribute("data-src");
image.setAttribute("src", src);
// handle the css of the container
const container = image.parentElement;
container.classList.add("lazy-container--loaded");
};
So everything wrapped together in a code sandbox looks like this:
Ok so for the php templates the solution is straight forward: let’s take a featured image as an example and also calculate the ratio mentioned above.
$image_id = get_post_thumbnail_id( $post->ID );
$image_obj = wp_get_attachment_image_src( $image_id, 'medium' );
$image_width = $image_obj[1];
$image_height = $image_obj[2];
$image_src = $image_obj[0];
$image_ratio = $image_width/$image_height * 100;
<div class="lazy-container" style="padding-bottom: <?php echo $image_ratio; ?> %;">
<img data-src="<?php echo $image_src; ?>" />
</div>
This is where the whole thing gets interesting. We need to manipulate the_content() and adjust the <img> to fit our markup.
This is the php snippet you can put into your functions.php and see the magic happens. Have fun!!!
add_filter('the_content', 'change_image_markup', 15);
function change_image_markup($the_content) {
//don't run on the backend
if( is_admin()) return $the_content;
$post = new DOMDocument();
libxml_use_internal_errors(true);
$post->loadHTML('' . $the_content);
$figures = $post->getElementsByTagName('figure');
// Iterate each img tag
foreach( $figures as $figure ) {
$img = $figure->firstChild;
$imgClass = $img->getAttribute('class');
$imgId = preg_replace('/[^0-9]/', '', $imgClass);
//size
$size = 'full';
$figureClass = $figure->getAttribute('class');
$figureClassArr = explode(' ', $figureClass);
foreach($figureClassArr as $item){
if (strpos($item, 'size') !== false) {
$position = strpos($item, "-") + 1;
$size = substr($item, $position);
break;
}
}
$thumbnail_obj = wp_get_attachment_image_src( $imgId, $size );
//if SVG lets break the loop
$thumbnail_type= get_post_mime_type($imgId);
if($thumbnail_type == 'image/svg+xml') return $post->saveHTML();
$thumbnail_width = $thumbnail_obj[1];
$thumbnail_height = $thumbnail_obj[2];
$thumbnail_url = $thumbnail_obj[0];
if($thumbnail_width >= $thumbnail_height ){
$thumbnail_ratio = $thumbnail_height/$thumbnail_width * 100;
} else {
$thumbnail_ratio = $thumbnail_width/$thumbnail_height * 100;
}
$wrapper = $post->createElement('div');
$wrapper->setAttribute('class','lazy-container');
$wrapper->setAttribute('style', 'padding-bottom:'. $thumbnail_ratio .'%');
$imgClone = $img->cloneNode();
$imgClone->removeAttribute('class');
$imgClone->removeAttribute('src');
$imgClone->setAttribute('class', $imgClass . ' lazy');
$imgClone->setAttribute('data-src', $thumbnail_url);
$wrapper->appendChild($imgClone);
$figure->replaceChild($wrapper, $img);
};
libxml_clear_errors();
return $post->saveHTML();
}