Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lazy load background images added via inline style attributes #1708

Merged
merged 20 commits into from
Nov 30, 2024

Conversation

ShyamGadde
Copy link
Contributor

Summary

Fixes #1035

Relevant technical choices

  • Applies only to background images defined inline using style attributes.
  • Excludes elements in the initial viewport from lazy loading.
  • Lazy loading for background images is enabled only when URL Metrics are available for both mobile and desktop.

@ShyamGadde
Copy link
Contributor Author

ShyamGadde commented Nov 28, 2024

TODO

  • Add Unit test cases

@ShyamGadde ShyamGadde marked this pull request as ready for review November 28, 2024 22:41
Copy link

github-actions bot commented Nov 28, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @rcwd.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: rcwd.

Co-authored-by: ShyamGadde <[email protected]>
Co-authored-by: westonruter <[email protected]>
Co-authored-by: erikyo <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

$processor->append_head_html(
'<style>
@media (scripting: enabled) {
.has-background.od-lazy-bg-image {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this has-background class coming from? I don't think we should depend on that being there as any element that has the inline style attribute with the background image is eligible for this lazy loading.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Gutenberg, the has-background class is typically added to blocks when a background image or color is applied. However, you're right that this might not be consistent, especially with custom blocks or themes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ce351c2.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great. I want to do some benchmarking to see the impact on load time performance to see if reducing network contention improves LCP.

Please also add some test cases for the new logic.


if ( ! $this->added_lazy_styles ) {
$processor->append_head_html(
'<style>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be pulled in from a CSS file like we're doing for the lazy script? In this way we can use the minified version when not in SCRIPT_DEBUG mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the Webpack setup doesn't minify CSS files the same way it handles JavaScript, as the TerserPlugin (used by CopyWebpackPlugin) is specifically for JavaScript minification. I'll explore other approaches to address this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess wp-scripts should have some way of doing this? I don't have a lot of experience with setting this up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While wp-scripts does handle CSS minification as part of its default build pipeline when importing CSS in JavaScript, it doesn't offer a direct solution for standalone CSS files like these, especially when the goal is to output them in the same directory as the source files.

I think it would be better to use a minifier like cssnano—which is also used by the postcss-loader in the Webpack configuration provided by wp-scripts—through a Webpack plugin like CssMinimizerPlugin, with identical minimizerOptions as in wp-scripts to ensure consistency with the wp-scripts build process.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in a21dec4.

@westonruter westonruter added [Type] Enhancement A suggestion for improvement of an existing feature [Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) labels Nov 29, 2024
@westonruter westonruter added this to the image-prioritizer n.e.x.t milestone Nov 29, 2024
@ShyamGadde
Copy link
Contributor Author

@westonruter

While working on the Webpack configuration, I noticed that most plugin setups (except those copying library files from node_modules) primarily handle JavaScript and CSS minification on a per-plugin basis. This requires updating CopyWebpackPlugin patterns whenever new assets are added or renamed.

To streamline this, I thought we could create a separate, generalized Webpack configuration that automates asset minification across all plugins. This approach would:

  • Automatically detect .js and .css files in plugin directories, excluding build directory and already minified files.
  • Minify and save the output alongside the source files (e.g., .js -> .min.js).
  • Eliminate the need for manual updates (for patterns defined in CopyWebpackPlugin) when adding or renaming assets.

Here's a potential implementation:

/**
 * Webpack Config: Minify Plugin Assets
 *
 * @return {Object} Webpack configuration
 */
const minifyPluginAssets = () => {
	return {
		...sharedConfig,
		name: 'minify-plugin-assets',
		plugins: [
			new CopyWebpackPlugin({
				patterns: [
					{
						from: `plugins/**/*.js`,
						to: ({ absoluteFilename }) =>
							absoluteFilename.replace(/\.js$/, '.min.js'),
						globOptions: {
							ignore: ['**/build/**', '**/*.min.js'],
						},
					},
					{
						from: `plugins/**/*.css`,
						to: ({ absoluteFilename }) =>
							absoluteFilename.replace(/\.css$/, '.min.css'),
						transform: {
							transformer: cssMinifyTransformer,
							cache: false,
						},
						globOptions: {
							ignore: ['**/build/**', '**/*.min.css'],
						},
					},
				],
			}),
			new WebpackBar({
				name: 'Minifying Plugin Assets',
				color: '#2196f3',
			}),
		],
	};
};

Would this approach be worth considering for a future enhancement or perhaps as a replacement for some of the existing configurations? It could reduce maintenance overhead and improve consistency.

PS: The configuration could also be adjusted to target assets for only the plugin being built during production builds if that aligns better with the workflow.

@westonruter
Copy link
Member

Would this approach be worth considering for a future enhancement or perhaps as a replacement for some of the existing configurations? It could reduce maintenance overhead and improve consistency.

@ShyamGadde That sounds good! Please do open a new enhancement issue for that.

@westonruter
Copy link
Member

I did some Web Vitals benchmarking. I tested with a page that had a featured image set so that it is the LCP element, and then far down the page I added a Cover block with a parallax background image and a Group block also with a different assigned background image. I did not see a consistent improvement to LCP over testing hundreds of iterations, which is largely to be expected. I did, however, see a large reduction in the initial page weight. Since there is no srcset for background images, their full (scaled) sizes are the ones that get downloaded. So whereas before I had:

  1. IMG which downloaded a 174KB bison1-1024x668.jpg (downsized thanks to srcset)
  2. background-image which downloaded a 1.3MB bison2-scaled.jpg
  3. background-image which downloaded a 708KB bison3-scaled.jpg

After only the IMG is initially downloaded and the two background-images were not initially loaded. So that's a ~90% reduction in bytes initially transferred.

Copy link
Member

@westonruter westonruter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

Comment on lines -6 to -11
$outside_viewport_rect = array_merge(
$test_case->get_sample_dom_rect(),
array(
'top' => 100000,
)
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

@@ -12,7 +12,7 @@
</body>
</html>
',
// There should be no data-od-xpath added to the DIV because it is using a data: URL for the background-image.
// There should be no data-od-xpath added to the DIV because it is using a background-color style.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch

@westonruter westonruter changed the title Lazy load background images Lazy load background images added via inline style attributes Nov 30, 2024
@westonruter westonruter merged commit d7bd6fa into WordPress:trunk Nov 30, 2024
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Plugin] Image Prioritizer Issues for the Image Prioritizer plugin (dependent on Optimization Detective) [Type] Enhancement A suggestion for improvement of an existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimization Detective: Implement lazy-loading of background images
2 participants