{"id":3350,"date":"2015-07-13T14:20:15","date_gmt":"2015-07-13T12:20:15","guid":{"rendered":"https:\/\/en.florianbrinkmann.de\/?p=3350"},"modified":"2020-02-09T11:00:01","modified_gmt":"2020-02-09T10:00:01","slug":"responsive-images-and-lazy-loading-in-wordpress","status":"publish","type":"post","link":"https:\/\/florianbrinkmann.com\/en\/responsive-images-and-lazy-loading-in-wordpress-3350\/","title":{"rendered":"Responsive images and lazy loading in WordPress"},"content":{"rendered":"\n<p>The technique responsive images prevents users from downloading unnecessary large images. Lazy loading only loads images if they are almost in the visible area, so the user does not download images he does not see. This post shows you how to use lazy loading with responsive images in WordPress.<\/p>\n\n\n\n<!--more-->\n\n\n\n<div class=\"update-box\">\n<p><strong>Update from January 19, 2017<\/strong>: Since WordPress 4.4 you do not need the RICG Responsive Images plugin anymore \u2014 the responsive images feature was merged into the core with this release.<\/p>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Lazy loading and responsive images for WordPress with \u00bblazySizes\u00ab<\/h2>\n\n\n\n<p>For implementing responsive images into WordPress, there is the <em>RICG Responsive Images<\/em> plugin. Because the WordPress.org account is listed as one of the developers, the feature will probably be merged into the core (was merged into WordPress 4.4).<\/p>\n\n\n\n<p>We use the script <em><a href=\"http:\/\/afarkas.github.io\/lazysizes\/\">lazySizes<\/a><\/em> for lazy loading the responsive images. To get this working, we have to modify the responsive images markup. The <code class=\"lang-markup\">srcset<\/code> and <code class=\"lang-markup\">sizes<\/code> attributes have to be changed into <code class=\"lang-markup\">data-srcset<\/code> and <code class=\"lang-markup\">data-sizes<\/code>. Besides that, we need to add the class <code class=\"lang-markup\">lazyload<\/code> and remove the <code class=\"lang-markup\">src<\/code> attribute.<\/p>\n\n\n\n<p>To add a fallback for deactivated JavaScript, we generate a <code class=\"lang-markup\">noscript<\/code> element. Let us begin with the first code part:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Modifying the page and post content<\/h2>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml\"><span class=\"php\"><span class=\"hljs-meta\">&lt;?php<\/span>\ndefined( <span class=\"hljs-string\">'ABSPATH'<\/span> ) <span class=\"hljs-keyword\">or<\/span> <span class=\"hljs-keyword\">die<\/span>( <span class=\"hljs-string\">\"Nothing to see!\"<\/span> );\n<span class=\"hljs-comment\">\/**\n * Plugin Name: Lazy Load Responsive Images\n *\/<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">lazy_load_responsive_images<\/span> <span class=\"hljs-params\">( $content )<\/span> <\/span>{\n   <span class=\"hljs-keyword\">if<\/span> ( <span class=\"hljs-keyword\">empty<\/span>( $content ) ) {\n      <span class=\"hljs-keyword\">return<\/span> $content;\n   }\n   $dom = <span class=\"hljs-keyword\">new<\/span> DOMDocument();\n   libxml_use_internal_errors( <span class=\"hljs-keyword\">true<\/span> );\n   $dom-&gt;loadHTML( $content );\n   libxml_clear_errors();\n   <span class=\"hljs-keyword\">foreach<\/span> ( $dom-&gt;getElementsByTagName( <span class=\"hljs-string\">'img'<\/span> ) <span class=\"hljs-keyword\">as<\/span> $img ) {\n      <span class=\"hljs-keyword\">if<\/span> ( $img-&gt;hasAttribute( <span class=\"hljs-string\">'sizes'<\/span> ) &amp;&amp; $img-&gt;hasAttribute( <span class=\"hljs-string\">'srcset'<\/span> ) ) {\n         $sizes_attr = $img-&gt;getAttribute( <span class=\"hljs-string\">'sizes'<\/span> );\n         $srcset     = $img-&gt;getAttribute( <span class=\"hljs-string\">'srcset'<\/span> );\n         $img-&gt;setAttribute( <span class=\"hljs-string\">'data-sizes'<\/span>, $sizes_attr );\n         $img-&gt;setAttribute( <span class=\"hljs-string\">'data-srcset'<\/span>, $srcset );\n         $img-&gt;removeAttribute( <span class=\"hljs-string\">'sizes'<\/span> );\n         $img-&gt;removeAttribute( <span class=\"hljs-string\">'srcset'<\/span> );\n         $src = $img-&gt;getAttribute( <span class=\"hljs-string\">'src'<\/span> );\n         <span class=\"hljs-keyword\">if<\/span> ( ! $src ) {\n            $src = $img-&gt;getAttribute( <span class=\"hljs-string\">'data-noscript'<\/span> );\n         }\n      } <span class=\"hljs-keyword\">else<\/span> {\n         $src = $img-&gt;getAttribute( <span class=\"hljs-string\">'src'<\/span> );\n         <span class=\"hljs-keyword\">if<\/span> ( ! $src ) {\n            $src = $img-&gt;getAttribute( <span class=\"hljs-string\">'data-noscript'<\/span> );\n         }\n         $img-&gt;setAttribute( <span class=\"hljs-string\">'data-src'<\/span>, $src );\n      }\n      $classes = $img-&gt;getAttribute( <span class=\"hljs-string\">'class'<\/span> );\n      $classes .= <span class=\"hljs-string\">\" lazyload\"<\/span>;\n      $img-&gt;setAttribute( <span class=\"hljs-string\">'class'<\/span>, $classes );\n      $img-&gt;removeAttribute( <span class=\"hljs-string\">'src'<\/span> );\n      $noscript      = $dom-&gt;createElement( <span class=\"hljs-string\">'noscript'<\/span> );\n      $noscript_node = $img-&gt;parentNode-&gt;insertBefore( $noscript, $img );\n      $noscript_img  = $dom-&gt;createElement( <span class=\"hljs-string\">'IMG'<\/span> );\n      $noscript_img-&gt;setAttribute( <span class=\"hljs-string\">'class'<\/span>, $classes );\n      $new_img = $noscript_node-&gt;appendChild( $noscript_img );\n      $new_img-&gt;setAttribute( <span class=\"hljs-string\">'src'<\/span>, $src );\n      $content = $dom-&gt;saveHTML();\n   }\n\n   <span class=\"hljs-keyword\">return<\/span> $content;\n}\nadd_filter( <span class=\"hljs-string\">'the_content'<\/span>, <span class=\"hljs-string\">'lazy_load_responsive_images'<\/span>, <span class=\"hljs-number\">20<\/span> );<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Before displaying the post or page in the frontend, we filter its content with the function <code class=\"lang-php\">lazy_load_responsive_images<\/code>. If the post content is empty, we just return <code class=\"lang-php\">$content<\/code> without modification. To modify the image attributes to meet our needs, we use the PHP <code class=\"lang-php\">DOM<\/code> classes (thanks for the hint to <a href=\"http:\/\/unserkaiser.com\/\">Franz Josef Kaiser<\/a>).<\/p>\n\n\n\n<p>At first, we create a new <code class=\"lang-php\">DOMDocument()<\/code> object. With the method <code class=\"lang-php\">loadHTML()<\/code>, we load the HTML code from the <code class=\"lang-php\">$content<\/code> variable, which holds the content of the post. <a href=\"http:\/\/stackoverflow.com\/questions\/6090667\/php-domdocument-errors-warnings-on-html5-tags\/6090728#6090728\">To avoid warnings with this method<\/a>, which seems not to know HTML5 elements yet, the lines above and below the <code class=\"lang-php\">loadHTML()<\/code> call are necessary.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Modification of images which were added with installed RICG Responsive Images plugin<\/h3>\n\n\n\n<p>Next, we loop through each <code class=\"lang-markup\">img<\/code> element. Because the responsive images plugin does not modify images which were added before the plugin activation, we have to check if the image is a responsive image. To do that, we check for the attributes <code class=\"lang-markup\">sizes<\/code> and <code class=\"lang-markup\">srcset<\/code>. If they are there, we get the content from both with the <code class=\"lang-php\">getAttribute()<\/code> method.<\/p>\n\n\n\n<p>After that, we need to set the new attributes <code class=\"lang-php\">data-sizes<\/code> and <code class=\"lang-php\">data-srcset<\/code> to the <code class=\"lang-markup\">img<\/code> element with the <code class=\"lang-php\">setAttribute()<\/code> method. We use the respective variable with the result of the <code class=\"lang-php\">getAttribute()<\/code> call as the second parameter. After doing this, we can remove the <code class=\"lang-markup\">sizes<\/code> and <code class=\"lang-markup\">srcset<\/code> attributes and save the image URL for the <code class=\"lang-markup\">noscript<\/code> image which will be added later. For that, we simply use the value of the <code class=\"lang-markup\">src<\/code> attribute. If no <code class=\"lang-markup\">src<\/code> attribute is given (for example this is the case for galleries), we use the <code class=\"lang-markup\">data-noscript<\/code> attribute.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Modify images without <code class=\"lang-markup\">sizes<\/code> and <code class=\"lang-markup\">srcset<\/code> attribute<\/h3>\n\n\n\n<p>Images which were added to posts or pages before installing the responsive images plugin neither have a <code class=\"lang-markup\">sizes<\/code> nor a <code class=\"lang-markup\">srcset<\/code> attribute. For these images, we fetch the URL like for the other images and add the <code class=\"lang-markup\">data-src<\/code> attribute.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Adding lazy load class and <code class=\"lang-markup\">noscript<\/code> element<\/h3>\n\n\n\n<p>Finally, we have to add a class to the images to get the lazy loading plugin working and create the <code class=\"lang-markup\">noscript<\/code> element. First, we save the existing classes of the image in the <code class=\"lang-php\">$classes<\/code> variable and append the class <code class=\"lang-markup\"> lazyload<\/code> in the next line. After that, we add the updated class string to the image again. Now we remove the <code class=\"lang-markup\">src<\/code> attribute (if it still exists).<\/p>\n\n\n\n<p>Next, we create a new <code class=\"lang-markup\">noscript<\/code> element, which is inserted before the image. Besides that, we need an <code class=\"lang-markup\">img<\/code> element which gets the classes of the original image (I do not know why it has to be <code class=\"lang-markup\">IMG<\/code> here, but it does not work with <code class=\"lang-markup\">img<\/code>). After that, we append the image to the <code class=\"lang-markup\">noscript<\/code> element and add the <code class=\"lang-markup\">src<\/code> attribute.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Modifying image attributes of gallery images and post thumbnails<\/h2>\n\n\n\n<p>To modify the attributes of gallery images and post thumbnails, WordPress gives us the <code class=\"lang-php\">wp_get_attachment_image_attributes<\/code> filter:<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">lazy_load_responsive_images_modify_post_thumbnail_attr<\/span><span class=\"hljs-params\">( $attr, $attachment, $size )<\/span> <\/span>{\n   <span class=\"hljs-keyword\">if<\/span> ( <span class=\"hljs-keyword\">isset<\/span>( $attr&#91;<span class=\"hljs-string\">'sizes'<\/span>] ) ) {\n      $data_sizes = $attr&#91;<span class=\"hljs-string\">'sizes'<\/span>];\n      <span class=\"hljs-keyword\">unset<\/span>( $attr&#91;<span class=\"hljs-string\">'sizes'<\/span>] );\n      $attr&#91;<span class=\"hljs-string\">'data-sizes'<\/span>] = $data_sizes;\n   }\n\n   <span class=\"hljs-keyword\">if<\/span> ( <span class=\"hljs-keyword\">isset<\/span>( $attr&#91;<span class=\"hljs-string\">'srcset'<\/span>] ) ) {\n      $data_srcset = $attr&#91;<span class=\"hljs-string\">'srcset'<\/span>];\n      <span class=\"hljs-keyword\">unset<\/span>( $attr&#91;<span class=\"hljs-string\">'srcset'<\/span>] );\n      $attr&#91;<span class=\"hljs-string\">'data-srcset'<\/span>]   = $data_srcset;\n      $attr&#91;<span class=\"hljs-string\">'data-noscript'<\/span>] = $attr&#91;<span class=\"hljs-string\">'src'<\/span>];\n      <span class=\"hljs-keyword\">unset<\/span>( $attr&#91;<span class=\"hljs-string\">'src'<\/span>] );\n   }\n\n   $attr&#91;<span class=\"hljs-string\">'class'<\/span>] .= <span class=\"hljs-string\">' lazyload'<\/span>;\n\n   <span class=\"hljs-keyword\">return<\/span> $attr;\n}\nadd_filter( <span class=\"hljs-string\">'wp_get_attachment_image_attributes'<\/span>, <span class=\"hljs-string\">'lazy_load_responsive_images_modify_post_thumbnail_attr'<\/span>, <span class=\"hljs-number\">20<\/span>, <span class=\"hljs-number\">3<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>First, we check if the <code class=\"lang-markup\">sizes<\/code> attribute is set. If this is the case, we save its content to a variable, remove the attribute and add a <code class=\"lang-markup\">data-sizes<\/code> attribute. We do the same with the <code class=\"lang-markup\">srcset<\/code> attribute. Additionally, we add the <code class=\"lang-markup\">data-noscript<\/code> attribute and remove the <code class=\"lang-markup\">src<\/code> attribute.<\/p>\n\n\n\n<p>Finally, we add the class <code class=\"lang-markup\">lazyload<\/code> to existing classes. Now only one thing is missing:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><code class=\"lang-markup\">noscript<\/code> element for post thumbnails<\/h2>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">lazy_load_responsive_images_filter_post_thumbnail_html<\/span><span class=\"hljs-params\">( $html, $post_id, $post_thumbnail_id, $size, $attr )<\/span> <\/span>{\n   $dom = <span class=\"hljs-keyword\">new<\/span> DOMDocument();\n   libxml_use_internal_errors( <span class=\"hljs-keyword\">true<\/span> );\n   $dom-&gt;loadHTML( $html );\n   libxml_clear_errors();\n   <span class=\"hljs-keyword\">foreach<\/span> ( $dom-&gt;getElementsByTagName( <span class=\"hljs-string\">'img'<\/span> ) <span class=\"hljs-keyword\">as<\/span> $img ) {\n      $src     = $img-&gt;getAttribute( <span class=\"hljs-string\">'data-noscript'<\/span> );\n      $classes = $img-&gt;getAttribute( <span class=\"hljs-string\">'class'<\/span> );\n   }\n   $noscript_element = <span class=\"hljs-string\">\"&lt;noscript&gt;&lt;img src='\"<\/span> . $src . <span class=\"hljs-string\">\"' class='\"<\/span> . $classes . <span class=\"hljs-string\">\"'&gt;&lt;\/noscript&gt;\"<\/span>;\n   $html .= $noscript_element;\n\n   <span class=\"hljs-keyword\">return<\/span> $html;\n}\nadd_filter( <span class=\"hljs-string\">'post_thumbnail_html'<\/span>, <span class=\"hljs-string\">'lazy_load_responsive_images_filter_post_thumbnail_html'<\/span>, <span class=\"hljs-number\">10<\/span>, <span class=\"hljs-number\">5<\/span>);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Parts of this code should look familiar to you: We get the classes and URL of the image and create the <code class=\"lang-markup\">noscript<\/code> element.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Adding the lazy load scripts<\/h2>\n\n\n\n<p>Let us insert the lazy loading script and a small style sheet to come to an end.<\/p>\n\n\n<pre class=\"wp-block-code lang-php\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">lazy_load_responsive_images_script<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n   wp_enqueue_script( <span class=\"hljs-string\">'lazy_load_responsive_images_script-lazysizes'<\/span>, plugins_url() . <span class=\"hljs-string\">'\/lazy-load-responsive-images\/js\/lazysizes.js'<\/span>, <span class=\"hljs-string\">''<\/span>, <span class=\"hljs-literal\">false<\/span>, <span class=\"hljs-literal\">false<\/span> );\n   wp_enqueue_style( <span class=\"hljs-string\">'lazy_load_responsive_images_style'<\/span>, plugins_url() . <span class=\"hljs-string\">'\/lazy-load-responsive-images\/css\/lazy_load_responsive_images.css'<\/span> );\n}\nadd_action( <span class=\"hljs-string\">'wp_enqueue_scripts'<\/span>, <span class=\"hljs-string\">'lazy_load_responsive_images_script'<\/span>, <span class=\"hljs-number\">20<\/span>, <span class=\"hljs-number\">0<\/span> );<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>That is it. The style sheet just hides the original image, if JavaScript is deactivated. For that, a <code class=\"lang-markup\">no-js<\/code> class has to be given.<\/p>\n\n\n\n<p><strong>Tip<\/strong>: In the normal case, this should add a <code class=\"lang-markup\">noscript<\/code> element to all images. But if you are using a theme, which brings its own functions for displaying images, these will not get one. If you know a filter, which allows the modification of the HTML of all images, please leave a comment! \ud83d\ude42<\/p>\n\n\n\n<p><a href=\"https:\/\/wordpress.org\/plugins\/lazy-loading-responsive-images\/\">Marc uploaded the plugin to WordPress.org<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The technique responsive images prevents users from downloading unnecessary large images. Lazy loading only loads images if they are almost in the visible area, so the user does not download images he does not see. This post shows you how to use lazy loading with responsive images in WordPress.<\/p>\n","protected":false},"author":1,"featured_media":3353,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wpf_show_in_dewp_planet_feed":false,"flobn_post_versions":"","webmentions_disabled_pings":false,"webmentions_disabled":false,"lazy_load_responsive_images_disabled":false,"footnotes":""},"categories":[42],"tags":[],"class_list":["post-3350","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress-plugins"],"wp-worthy-pixel":{"ignored":false,"public":"355aaa9948854c3aa1f32ab727af147c","server":"vg01.met.vgwort.de","url":"https:\/\/vg01.met.vgwort.de\/na\/355aaa9948854c3aa1f32ab727af147c"},"wp-worthy-type":"normal","_links":{"self":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/3350","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/comments?post=3350"}],"version-history":[{"count":4,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/3350\/revisions"}],"predecessor-version":[{"id":5944,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/3350\/revisions\/5944"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/media\/3353"}],"wp:attachment":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/media?parent=3350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/categories?post=3350"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/tags?post=3350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}