Skip to content

Commit 6b47600

Browse files
ebinnionoskosk
authored andcommitted
PWA: Adds lazy image loading module (#8093)
* Lazy Images: Add lazy images module to improve load time * Lazy Images: Only apply srcset if not falsey; Do not apply fade-in class * Lazy Images: Do not lazy load wp admin bar avatar * Lazy Images: First pass at upstream updates * Lazy Images: Move back to IntersectionObserver * Lazy Images: Adds doc for lazy images placeholder image * Lazy Images: Remove unused version * Lazy Images: Add support for Infinite Scroll * Lazy Images: Add top conflicting plugins * Lazy Images: Add attribution for basis of module * Lazy Images: Load image when within 200px vertically of viewport * Lazy Images: Add unit tests * Lazy Images: Set sizes attribute if stored in attribute * Lazy Images: Update IntersectionObserver polyfill to a GPLv2 compatible one * Lazy Images: Remove ignore for intersection-observer.js now that we inline the polyfill * Lazy Images: Add comment to clarify why filters not prefixed with _jetpack * Lazy Images: Add missing semicolon to please linter
1 parent 38be496 commit 6b47600

File tree

11 files changed

+559
-4
lines changed

11 files changed

+559
-4
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
_inc/client/**/test/*.js
2+
modules/lazy-images/js/lazy-images.js

class.jetpack.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ class Jetpack {
204204
'XML Sitemaps' => 'xml-sitemaps/xml-sitemaps.php',
205205
'MSM Sitemaps' => 'msm-sitemap/msm-sitemap.php',
206206
),
207+
'lazy-images' => array(
208+
'Lazy Load' => 'lazy-load/lazy-load.php',
209+
'BJ Lazy Load' => 'bj-lazy-load/bj-lazy-load.php',
210+
'Lazy Load by WP Rocket' => 'rocket-lazy-load/rocket-lazy-load.php',
211+
),
207212
);
208213

209214
/**

modules/lazy-images.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/**
4+
* Module Name: Lazy Images
5+
* Module Description: Improve performance by loading images just before they scroll into view
6+
* Sort Order: 24
7+
* Recommendation Order: 14
8+
* First Introduced: 5.6.0
9+
* Requires Connection: No
10+
* Auto Activate: No
11+
* Module Tags: Appearance, Recommended
12+
* Feature: Appearance
13+
* Additional Search Queries: mobile, theme, performance
14+
*/
15+
16+
/**
17+
* This module relies heavily upon the Lazy Load plugin which was worked on by
18+
* Mohammad Jangda (batmoo), the WordPress.com VIP team, the TechCrunch 2011
19+
* redesign team, and Jake Goldman of 10up LLC.
20+
*
21+
* The JavaScript has been updated to rely on InterSection observer instead of
22+
* jQuery Sonar. Many thanks to Dean Hume (deanhume) and his example:
23+
* https://github.com/deanhume/lazy-observer-load
24+
*/
25+
26+
require_once( JETPACK__PLUGIN_DIR . 'modules/lazy-images/lazy-images.php' );
27+
Jetpack_Lazy_Images::instance();
42 Bytes
Loading

modules/lazy-images/js/lazy-images.js

Lines changed: 157 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
class Jetpack_Lazy_Images {
4+
private static $__instance = null;
5+
6+
/**
7+
* Singleton implementation
8+
*
9+
* @return object
10+
*/
11+
public static function instance() {
12+
if ( is_null( self::$__instance ) ) {
13+
self::$__instance = new Jetpack_Lazy_Images();
14+
}
15+
16+
return self::$__instance;
17+
}
18+
19+
/**
20+
* Registers actions
21+
*/
22+
private function __construct() {
23+
if ( is_admin() ) {
24+
return;
25+
}
26+
27+
/**
28+
* Whether the lazy-images module should load.
29+
*
30+
* This filter is not prefixed with jetpack_ to provide a smoother migration
31+
* process from the WordPress Lazy Load plugin.
32+
*
33+
* @module lazy-images
34+
*
35+
* @since 5.6.0
36+
*
37+
* @param bool true Whether lazy image loading should occur.
38+
*/
39+
if ( ! apply_filters( 'lazyload_is_enabled', true ) ) {
40+
return;
41+
}
42+
43+
add_action( 'wp_head', array( $this, 'setup_filters' ), 9999 ); // we don't really want to modify anything in <head> since it's mostly all metadata
44+
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
45+
46+
// Do not lazy load avatar in admin bar
47+
add_action( 'admin_bar_menu', array( $this, 'remove_filters' ), 0 );
48+
}
49+
50+
public function setup_filters() {
51+
add_filter( 'the_content', array( $this, 'add_image_placeholders' ), 99 ); // run this later, so other content filters have run, including image_add_wh on WP.com
52+
add_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), 11 );
53+
add_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), 11 );
54+
}
55+
56+
public function remove_filters() {
57+
remove_filter( 'the_content', array( $this, 'add_image_placeholders' ), 99 );
58+
remove_filter( 'post_thumbnail_html', array( $this, 'add_image_placeholders' ), 11 );
59+
remove_filter( 'get_avatar', array( $this, 'add_image_placeholders' ), 11 );
60+
}
61+
62+
public function add_image_placeholders( $content ) {
63+
// Don't lazyload for feeds, previews
64+
if ( is_feed() || is_preview() ) {
65+
return $content;
66+
}
67+
68+
// Don't lazy-load if the content has already been run through previously
69+
if ( false !== strpos( $content, 'data-lazy-src' ) ) {
70+
return $content;
71+
}
72+
73+
// Don't lazyload for amp-wp content
74+
if ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() ) {
75+
return $content;
76+
}
77+
78+
// This is a pretty simple regex, but it works
79+
$content = preg_replace_callback( '#<(img)([^>]+?)(>(.*?)</\\1>|[\/]?>)#si', array( __CLASS__, 'process_image' ), $content );
80+
81+
return $content;
82+
}
83+
84+
static function process_image( $matches ) {
85+
$old_attributes_str = $matches[2];
86+
$old_attributes_kses_hair = wp_kses_hair( $old_attributes_str, wp_allowed_protocols() );
87+
88+
if ( empty( $old_attributes_kses_hair['src'] ) ) {
89+
return $matches[0];
90+
}
91+
92+
$old_attributes = self::flatten_kses_hair_data( $old_attributes_kses_hair );
93+
$new_attributes = $old_attributes;
94+
95+
// Set placeholder and lazy-src
96+
$new_attributes['src'] = self::get_placeholder_image();
97+
$new_attributes['data-lazy-src'] = $old_attributes['src'];
98+
99+
// Handle `srcset`
100+
if ( ! empty( $new_attributes['srcset'] ) ) {
101+
$new_attributes['data-lazy-srcset'] = $old_attributes['srcset'];
102+
unset( $new_attributes['srcset'] );
103+
}
104+
105+
// Handle `sizes`
106+
if ( ! empty( $new_attributes['sizes'] ) ) {
107+
$new_attributes['data-lazy-sizes'] = $old_attributes['sizes'];
108+
unset( $new_attributes['sizes'] );
109+
}
110+
111+
$new_attributes_str = self::build_attributes_string( $new_attributes );
112+
113+
return sprintf( '<img %1$s><noscript>%2$s</noscript>', $new_attributes_str, $matches[0] );
114+
}
115+
116+
private static function get_placeholder_image() {
117+
/**
118+
* Allows plugins and themes to modify the placeholder image.
119+
*
120+
* This filter is not prefixed with jetpack_ to provide a smoother migration
121+
* process from the WordPress Lazy Load plugin.
122+
*
123+
* @module lazy-images
124+
*
125+
* @since 5.6.0
126+
*
127+
* @param string The URL to the placeholder image
128+
*/
129+
return apply_filters(
130+
'lazyload_images_placeholder_image',
131+
plugins_url( 'modules/lazy-images/images/1x1.trans.gif', JETPACK__PLUGIN_FILE )
132+
);
133+
}
134+
135+
private static function flatten_kses_hair_data( $attributes ) {
136+
$flattened_attributes = array();
137+
foreach ( $attributes as $name => $attribute ) {
138+
$flattened_attributes[ $name ] = $attribute['value'];
139+
}
140+
return $flattened_attributes;
141+
}
142+
143+
private static function build_attributes_string( $attributes ) {
144+
$string = array();
145+
foreach ( $attributes as $name => $value ) {
146+
if ( '' === $value ) {
147+
$string[] = sprintf( '%s', $name );
148+
} else {
149+
$string[] = sprintf( '%s="%s"', $name, esc_attr( $value ) );
150+
}
151+
}
152+
return implode( ' ', $string );
153+
}
154+
155+
public function enqueue_assets() {
156+
wp_enqueue_script(
157+
'jetpack-lazy-images',
158+
plugins_url( 'modules/lazy-images/js/lazy-images.js', JETPACK__PLUGIN_FILE ),
159+
array( 'jquery' ),
160+
JETPACK__VERSION,
161+
true
162+
);
163+
}
164+
}

modules/module-headings.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ function jetpack_get_module_i18n( $key ) {
8080
'description' => _x( 'Use LaTeX markup for complex equations and other geekery.', 'Module Description', 'jetpack' ),
8181
),
8282

83+
'lazy-images' => array(
84+
'name' => _x( 'Lazy Images', 'Module Name', 'jetpack' ),
85+
'description' => _x( 'Improve performance by loading images just before they scroll into view', 'Module Description', 'jetpack' ),
86+
),
87+
8388
'likes' => array(
8489
'name' => _x( 'Likes', 'Module Name', 'jetpack' ),
8590
'description' => _x( 'Give visitors an easy way to show they appreciate your content.', 'Module Description', 'jetpack' ),
@@ -291,6 +296,7 @@ function jetpack_get_module_i18n_tag( $key ) {
291296
// - modules/custom-css.php
292297
// - modules/gravatar-hovercards.php
293298
// - modules/infinite-scroll.php
299+
// - modules/lazy-images.php
294300
// - modules/minileven.php
295301
// - modules/photon.php
296302
// - modules/seo-tools.php
@@ -306,11 +312,8 @@ function jetpack_get_module_i18n_tag( $key ) {
306312
// - modules/sso.php
307313
'Developers' =>_x( 'Developers', 'Module Tag', 'jetpack' ),
308314

309-
// Modules with `Centralized Management` tag:
310-
// - modules/manage.php
311-
'Centralized Management' =>_x( 'Centralized Management', 'Module Tag', 'jetpack' ),
312-
313315
// Modules with `Recommended` tag:
316+
// - modules/lazy-images.php
314317
// - modules/manage.php
315318
// - modules/minileven.php
316319
// - modules/monitor.php
@@ -323,6 +326,10 @@ function jetpack_get_module_i18n_tag( $key ) {
323326
// - modules/stats.php
324327
'Recommended' =>_x( 'Recommended', 'Module Tag', 'jetpack' ),
325328

329+
// Modules with `Centralized Management` tag:
330+
// - modules/manage.php
331+
'Centralized Management' =>_x( 'Centralized Management', 'Module Tag', 'jetpack' ),
332+
326333
// Modules with `General` tag:
327334
// - modules/masterbar.php
328335
'General' =>_x( 'General', 'Module Tag', 'jetpack' ),

phpunit.xml.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@
9292
<testsuite name="pwa">
9393
<directory prefix="test_" suffix=".php">tests/php/pwa</directory>
9494
</testsuite>
95+
<testsuite name="lazy-images">
96+
<directory prefix="test_" suffix=".php">tests/php/modules/lazy-images</directory>
97+
</testsuite>
9598
</testsuites>
9699
<groups>
97100
<exclude>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<p>OK, so images can get quite complicated as we have a few variables to work with! For example the image below has had a caption entered in the WordPress image upload dialog box, this creates a [caption] shortcode which then in turn wraps the whole thing in a <code>div</code> with inline styling! Maybe one day they'll be able to use the <code>figure</code> and <code>figcaption</code> elements for all this. Additionally, images can be wrapped in links which, if you're using anything other than <code>color</code> or <code>text-decoration</code> to style your links can be problematic.</p>
2+
<div id="attachment_28" class="wp-caption alignnone" style="width: 510px"><a href="#"><img src="placeholder.jpg" alt="Your Alt Tag" title="bmxisbest" width="500" height="300" class="size-large wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_large.png"><noscript><img src="http://jetpacksite.com/img/img_large.png" alt="Your Alt Tag" title="bmxisbest" width="500" height="300" class="size-large wp-image-28"></noscript></a><p class="wp-caption-text">This is the optional caption.</p></div>
3+
<p>The next issue we face is image alignment, users get the option of <em>None</em>, <em>Left</em>, <em>Right</em> &amp; <em>Center</em>. On top of this, they also get the options of <em>Thumbnail</em>, <em>Medium</em>, <em>Large</em> &amp; <em>Fullsize</em>. You'll probably want to add floats to style the image position so important to remember to clear these to stop images popping below the bottom of your articles.</p>
4+
<img src="placeholder.jpg" alt="Your Alt Title" title="Your Title" width="300" height="200" class="alignright size-medium wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_medium.png"><noscript><img src="http://jetpacksite.com/img/img_medium.png" alt="Your Alt Title" title="Your Title" width="300" height="200" class="alignright size-medium wp-image-28"></noscript>
5+
<img src="placeholder.jpg" alt="Your Alt Title" title="Your Title" width="150" height="150" class="alignleft size-thumbnail wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img src="http://jetpacksite.com/img/img_thumb.png" alt="Your Alt Title" title="Your Title" width="150" height="150" class="alignleft size-thumbnail wp-image-28"></noscript>
6+
<img class="aligncenter size-medium wp-image-28" title="Your Title" src="placeholder.jpg" alt="Your Alt Title" width="300" height="200" data-lazy-src="http://jetpacksite.com/img/img_medium.png"><noscript><img class="aligncenter size-medium wp-image-28" title="Your Title" src="http://jetpacksite.com/img/img_medium.png" alt="Your Alt Title" width="300" height="200"></noscript>
7+
<img src="placeholder.jpg" alt="Your Alt Title" title="Your Title" width="840" height="300" class="alignnone size-full wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_full.png"><noscript><img src="http://jetpacksite.com/img/img_full.png" alt="Your Alt Title" title="Your Title" width="840" height="300" class="alignnone size-full wp-image-28"></noscript>
8+
<p>Additionally, to add further confusion, images can be wrapped inside paragraph content, lets test some examples here.<img src="placeholder.jpg" alt="Your Alt Title" title="Your Title" width="300" height="200" class="alignright size-medium wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_medium.png"><noscript><img src="http://jetpacksite.com/img/img_medium.png" alt="Your Alt Title" title="Your Title" width="300" height="200" class="alignright size-medium wp-image-28"></noscript>
9+
Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas sed diam eget risus varius blandit sit amet non magna. Aenean lacinia bibendum nulla sed consectetur.<img src="placeholder.jpg" alt="Your Alt Title" title="Your Title" width="150" height="150" class="alignleft size-thumbnail wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img src="http://jetpacksite.com/img/img_thumb.png" alt="Your Alt Title" title="Your Title" width="150" height="150" class="alignleft size-thumbnail wp-image-28"></noscript>Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Maecenas sed diam eget risus varius blandit sit amet non magna. Aenean lacinia bibendum nulla sed consectetur.<img src="placeholder.jpg" alt="Your Alt Title" title="Your Title" width="150" height="150" class="aligncenter size-thumbnail wp-image-28" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img src="http://jetpacksite.com/img/img_thumb.png" alt="Your Alt Title" title="Your Title" width="150" height="150" class="aligncenter size-thumbnail wp-image-28"></noscript>Aenean lacinia bibendum nulla sed consectetur. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Donec ullamcorper nulla non metus auctor fringilla. Aenean lacinia bibendum nulla sed consectetur.</p>
10+
<p>And then... Finally, users can insert a WordPress [gallery], which is kinda ugly and comes with some CSS stuck into the page to style it (which doesn't actually validate, nor does the markup for the gallery). The amount of columns in the gallery is also changable by the user, but the default is three so we'll work with that for our example with an added fouth image to test verticle spacing.</p>
11+
<style type="text/css">
12+
#gallery-1 {
13+
margin: auto;
14+
}
15+
#gallery-1 .gallery-item {
16+
float: left;
17+
margin-top: 10px;
18+
text-align: center;
19+
width: 33%;
20+
}
21+
#gallery-1 img {
22+
border: 2px solid #cfcfcf;
23+
}
24+
#gallery-1 .gallery-caption {
25+
margin-left: 0;
26+
}
27+
</style>
28+
29+
<div id="gallery-1" class="gallery galleryid-1 gallery-columns-3 gallery-size-thumbnail"><dl class="gallery-item">
30+
<dt class="gallery-icon">
31+
<a href="#" title="Your Title"><img width="150" height="150" src="placeholder.jpg" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img width="150" height="150" src="http://jetpacksite.com/img/img_thumb.png" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title"></noscript></a>
32+
</dt></dl><dl class="gallery-item">
33+
<dt class="gallery-icon">
34+
<a href="#" title="Your Title"><img width="150" height="150" src="placeholder.jpg" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img width="150" height="150" src="http://jetpacksite.com/img/img_thumb.png" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title"></noscript></a>
35+
</dt></dl><dl class="gallery-item">
36+
<dt class="gallery-icon">
37+
<a href="#" title="Your Title"><img width="150" height="150" src="placeholder.jpg" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img width="150" height="150" src="http://jetpacksite.com/img/img_thumb.png" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title"></noscript></a>
38+
</dt></dl><br style="clear: both"><dl class="gallery-item">
39+
<dt class="gallery-icon">
40+
<a href="#" title="Your Title"><img width="150" height="150" src="placeholder.jpg" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title" data-lazy-src="http://jetpacksite.com/img/img_thumb.png"><noscript><img width="150" height="150" src="http://jetpacksite.com/img/img_thumb.png" class="attachment-thumbnail" alt="Your Alt Title" title="Your Title"></noscript></a>
41+
</dt></dl>
42+
<br style="clear: both;">
43+
</div>

0 commit comments

Comments
 (0)