A topic of contention – should you use PHP or jQuery (or any other of the plethora of JS framework offerings!) to manipulate elements in the DOM? This is something I had to do recently and with the help of some great developers, I managed to utilize PHP’s DOMDocument()
to
Table of Contents
Why do this?
“Why were you even looking at this” I hear you ask! Well my good friend, I’m a MASSIVE fan of my theme and with that I’m an even bigger fan of the devs that created it!
My current theme makes use of the amazing lazysizes library by aFarkas. However, the images didn’t have placeholders so at times there was some “jank”. Generally, things were quick enough that this would go unnoticed, but on slower (mobile) connections it was VERY noticeable.
I started reading into lazy loading more and more and found that lazysizes actually has a “blur-up” plugin, which essentially uses a low-resolution placeholder image, with a CSS filter: blur;
applied. Once the full resolution image has finished loading in the background, lazysizes then transition
So, I reached out to the theme devs. They said it’s something they had considered, but ultimately didn’t implement due to time constraints. So I took it upon myself to investigate whether it was possible to do!
The starting point
I started with the relatively “simple” regex that was included as part of the theme, which was all well and good, but it felt a bit… old school. Side note: complex regex is WAY beyond my understanding – even simple regex is very confusing to an untrained eye!!
function willstocks_content_images( $content ) { global $post; $pattern ="/<img(.*?)class=\"(.*?)\"(.*?)src=(.*?)>/i"; $replacement = '<img$1class="$2 lazyload blur-up"$3data-src=$4>'; $content = preg_replace( $pattern, $replacement, $content ); return $content; } add_filter( 'the_content', 'willstocks_content_images' );*/
OK, so that works fine… why bother going any further? Well, sometimes I just can’t help myself. I fiddle until I can fiddle no more – I love the bleeding edge of tech!
At this point, the guys that develop the theme had a little spare time! We threw a bunch of ideas back and forth. The first idea was to handle the DOM manipulation via jQuery. However I personally wasn’t a fan of this idea, simply because it pushes the responsibility client-side. Due to various different devices, processes, browser engines and network restrictions, it was possible that jQuery may not load on all clients and therefore the classes wouldn’t be applied! I also wanted full control over the content you guys are seeing – I don’t want weird and different experiences, just because you can’t load jQuery or have a strange browser or for any other reason.
The next step: Manipulating the DOM via PHP
So I turned back to PHP. The guys supporting the theme were also in agreeance – it is much better to handle this server-side where I knew all possible variables. I can also ensure that all visitors see the same thing, no matter what device or browser!
Before I go much further, let me be honest here – I’m not hugely skilled in PHP or WordPress functions. I dabble, but I’m no developer.
On with the show… ?
The actual code!
So the below is the function that between me and my friendly neighbourhood theme devs came up with:
function willstocks_blurup_content_images( $content ) { if ( !is_admin() ) { global $post; $content = preg_replace( '/-([^-]*(\d+)x(\d+)\.((?:png|jpeg|jpg|gif|bmp)))"/', '.${4}"', $content ); $dom = new DOMDocument(); libxml_use_internal_errors(true); $dom->loadHTML( mb_convert_encoding( $content, 'HTML-ENTITIES', 'UTF-8' ) ); libxml_clear_errors(); $imgs = $dom->getElementsByTagName( 'img' ); foreach ( $imgs as $img ) { $image_url = $img->getAttribute( 'src' ); $image_id = attachment_url_to_postid( $image_url ); $new_src = wp_get_attachment_image_src( $image_id, 'willstocks_full_100' ); $img->setAttribute( 'src', $new_src[0] ); if ( ! wp_get_attachment_image_srcset( $image_id, 'full' ) ) { $img->setAttribute( 'data-src', $image_url ); } $image_classes = $img->getAttribute( 'class' ); $img->setAttribute( 'class', 'lazyload blur-up ' . $image_classes ); } $content = $dom->saveHTML(); $content = str_replace( 'srcset', 'data-srcset', $content ); return $content; } } add_filter( 'the_content', 'willstocks_blurup_content_images' );
Explanation of what’s happening
We had to make use of libxml_use_internal_errors(true);
and libxml_clear_errors();
as for some reason, this function was throwing some error messages on the frontend… I haven’t gotten around to clearing that up yet! This little function basically suppresses the errors from the front end. We later clear that set of errors to ensure we’re not wasting memory.
One thing I need to do after clearing the errors libxml_use_internal_errors(false);
Next up, we’re grabbing the actual DOM document itself with $dom->loadHTML();
mb_convert_encoding( $content, 'HTML-ENTITIES', 'UTF-8' )
– the reason for this is that while implementing this function, I noticed I was seeing a whole load of random and strange characters sprinkled throughout my post content. GOOGLE TO THE RESCUE! As it turns out, it was simply a case $dom->loadHTML();
Get the images
Now for the fun bit – this is where we actually make the changes to the image classes! So we’ve got this piece of code:
$imgs = $dom->getElementsByTagName( 'img' ); foreach ( $imgs as $img ) { $image_url = $img->getAttribute( 'src' ); $image_id = attachment_url_to_postid( $image_url );
What we’re doing here is grabbing <img>
src
src
attachment_url_to_postid
Get the low-res placeholder image
The reason we need to do this is so that we can tie that particular image back to its appropriate image sizes, via:
$new_src = wp_get_attachment_image_src( $image_id, 'willstocks_full_100' ); $img->setAttribute( 'src', $new_src[0] );
We’re using wp_get_attachment_image_src
willstocks_full_100
functions.php
add_image_size( 'willstocks_full_100', 100 );
– so no hard cropping, images will retain their aspect ratio which means no jank!) and storing it as $new_src
$img
src
(URL) from and replace that URL with $new_src
Note: I went with a 100px wide image as the placeholder as in my case. It was a low-enough resolution that file size was small (the point of diminishing returns), but also with enough quality that you could just about make out the image content.
Set the original to the lazy loaded image
if ( ! wp_get_attachment_image_srcset( $image_id, 'full' ) ) { $img->setAttribute( 'data-src', $image_url ); }
At this point, we’re now making sure to ignore “full” sized images, as typically these images are things like GIF’s. Now, we set the data-src
value to the original src
value (remember, we had $image_url = $img->getAttribute( 'src' );
further up!). This means that our original image now becomes the lazy loaded image, as lazysizes will swap out src
and data-src
when the images come into the view-port!
Add the new class
So, we’ve made sure the images are correctly lined up and we know which images we want to add our new class to… so let’s do it!
$image_classes = $img->getAttribute( 'class' ); $img->setAttribute( 'class', 'lazyload blur-up ' . $image_classes );
First up, we need to get and store the existing class
attributes of the img
tag (which we’re doing with $image_classes
. Once we’ve done that, we can use setAttribute
to add our new classes to the img
tag and then re-add the original classes back on the end!
FINALLY! Return the images with the new classes
THE FINAL STEP! All we need to do now is return the images with the new classes. First up, we need to save everything we’ve been doing back to our $content
variable our original content! For this, we’ll use the saveHTML()
function.
$content = $dom->saveHTML(); $content = str_replace( 'srcset', 'data-srcset', $content ); return $content;
Next up, to make sure we’re only serving the appropriately sized image (because WordPress serves responsive images as standard!), we’re going to make use of PHP’s str_replace
function to move the img
‘s srcset
values (aka WordPress’ responsive images) into the data-srcset
attribute. We’re saving all of these changes in $content
which means there’s nothing else to do.
Now, we can return $content;
! Et voila… all <img>
tags will now show up as (example based on my site):
<img class="lazyload blur-up" src="https://cdn.willstocks.co.uk/image-100x45" data-data-srcset="https://cdn.willstocks.co.uk/image-420x236.jpg 420w, https://cdn.willstocks.co.uk/image-11x7.jpg 11w, https://cdn.willstocks.co.uk/image-300x169.jpg 300w, https://cdn.willstocks.co.uk/image-768x432.jpg 768w, https://cdn.willstocks.co.uk/image-100x56.jpg 100w, https://cdn.willstocks.co.uk/image-840x473.jpg 840w, https://cdn.willstocks.co.uk/image-21x13.jpg 21w, https://cdn.willstocks.co.uk/image.jpg 1000w" data-sizes="auto">
As you can see, this has all of the responsive image sizes I have setup for my site. The classes are also applied and you can review the source for my site and you’ll see that all of the images have the lazyload
class. Scroll those images into view and boom – lazyloaded
.
Afterthoughts
This was honestly a fun learning experience for me! I got more insight into the intricacies of PHP and what I can do with it. I don’t know whether it’s the most efficient method for completing this task, but it feels much better than the original regex… at least more supportable! ??
Also – shoutout to the guys that develop/support my theme, they’re honestly great and have helped me out a tonne!!!
If there’s a better way to do this (preferably server-side), please let me know if the comments down below! Might even be a slightly more efficient tweak that could be made to the function??? ?