Image loading optimization enhancements in 6.4
WordPress 6.4 comes with several enhancements to the wp_get_loading_optimization_attributes()
function which was introduced in 6.3 as a central place to manage loading optimization attributes, specifically for images and iframes.
Quick recap: Loading optimization attributes are those such as loading="lazy"
fetchpriority="high"
, which can be added to certain HTML tags to optimize loading performance in the browser.
Simplified complex logic
The logic of the wp_get_loading_optimization_attributes()
is generally quite complex, as it caters for several factors that influence when to apply certain loading optimization attributes. When the function was originally introduced, it was particularly hard to follow, since it included lots of early returns as well as closures. This was mostly due to maintaining its original code paths that came from the deprecated wp_get_loading_attr_default()
function as much as possible to avoid breakage.
While the function logic is still complex in WordPress 6.4, it has been notably simplified, taking a more sequential and thus easier to follow approach. This facilitated the implementation of further enhancements such as those outlined below.
Please refer to Trac ticket #58891 for additional details on these changes.
Managing the decoding="async"
attribute
The decoding="async"
attribute has been present on images by default since WordPress 6.1 (see Trac ticket #53232). As of WordPress 6.4, the logic for applying the attribute has been consolidated in the wp_get_loading_optimization_attributes()
function, as the attribute is a perfect fit for that function.
Deprecated function
As part of that change, the wp_img_tag_add_decoding_attr()
function has been deprecated, as its logic is now incorporated into wp_img_tag_add_loading_optimization_attrs()
. Unless you are using the now deprecated function in your code, no changes should be needed after this update in regards to the decoding="async"
attribute. The change merely enables control over the attribute in a more consistent manner (also see the new filters introduced below).
If you are using the deprecated function and want to use a fully backward compatible replacement, you can implement a custom wrapper function such as the following:
function myplugin_img_tag_add_decoding_attr( $image, $context ) {
global $wp_version;
// For WP >= 6.4.
if ( version_compare( $wp_version, '6.4', '>=' ) ) {
$image = wp_img_tag_add_loading_optimization_attrs( $image, $context );
// Strip potential attributes added other than `decoding="async"`.
return str_replace(
array(
' loading="lazy"',
' fetchpriority="high"',
),
'',
$image
);
}
// For WP < 6.4.
return wp_img_tag_add_decoding_attr( $image, $context );
}
Please refer to Trac ticket #58892 for additional details on these changes.
New filters to control loading optimization attributes
With WordPress 6.4, two filters have been added to wp_get_loading_optimization_attributes()
which allow modifying or completely overriding the logic used to apply the loading optimization attributes:
- The
wp_get_loading_optimization_attributes
filter can be used to modify the results from the WordPress core logic. - The
pre_wp_get_loading_optimization_attributes
filter can be used to use entirely custom logic and effectively short-circuit the core function by returning a value other thanfalse
.
Below are a few examples on how these two filters could be used.
Filter usage examples
You could use the wp_get_loading_optimization_attributes
filter to ensure a specific image within post content receives the fetchpriority="high"
attribute while other ones do not:
function set_fetchpriority_high_on_specific_image( $loading_attrs, $tag_name, $attr, $context ) {
if ( 'img' === $tag_name ) {
if (
'the_content' === $context &&
isset( $attr['src'] ) &&
$attr['src'] === 'https://example.org/a-specific-image.jpg'
) {
$loading_attrs['fetchpriority'] = 'high';
} else {
unset( $loading_attrs['fetchpriority'] );
}
}
return $loading_attrs;
}
add_filter(
'wp_get_loading_optimization_attributes',
'set_fetchpriority_high_on_specific_image',
10,
4
);
Alternatively, you could use the wp_get_loading_optimization_attributes
filter to disable adding the fetchpriority="high"
attribute entirely:
function disable_fetchpriority_high( $loading_attrs ) {
unset( $loading_attrs['fetchpriority'] );
return $loading_attrs;
}
add_filter(
'wp_get_loading_optimization_attributes',
'disable_fetchpriority_high'
);
You could implement entirely custom logic in a plugin to detect which images appear in the viewport using client-side JavaScript logic or an external service and replace the function’s default logic with that:
function override_loading_optimization_attributes( $override, $tag_name, $attr, $context ) {
// Bail if another filter callback already overrode this.
if ( false !== $override ) {
return $override;
}
// Use custom logic to determine whether image is LCP and whether it appears above the fold.
if ( 'img' === $tag_name ) {
$is_lcp = custom_function_to_detect_whether_image_is_lcp_element( $attr );
$in_viewport = custom_function_to_detect_whether_image_is_above_the_fold( $attr );
/*
* Always add `decoding="async"`.
* Add `fetchpriority="high"` only to the LCP image.
* Add `loading="lazy"` to any image below the fold / outside the viewport.
*/
$loading_attrs = array( 'decoding' => 'async' );
if ( $is_lcp ) {
$loading_attrs['fetchpriority'] = 'high';
} elseif ( ! $in_viewport ) {
$loading_attrs['loading'] = 'lazy';
}
return $loading_attrs;
}
return $override;
}
add_filter(
'pre_wp_get_loading_optimization_attributes',
'override_loading_optimization_attributes',
10,
4
);
Please refer to Trac ticket #58893 for additional details on these changes.
Support for custom context values
As explained in the 6.3 dev note for the wp_get_loading_optimization_attributes()
function, initially it only supported specific context values used by WordPress core. This made it confusing to write custom code making use of that function as you would have been forced to use a context string used elsewhere in core in order to get the performance benefits.
This limitation has been addressed in WordPress 6.4: The function now supports arbitrary contexts, and for the most part does not apply context-specific optimizations. There are two exceptions to this which are the “template_part_header” and “get_header_image_tag” contexts. Images within these contexts will always be interpreted to be in the header. The list of these two context strings is filterable with a new wp_loading_optimization_force_header_contexts
filter, which allows images with other contexts to be always assumed to appear above the fold.
Context filter usage example
Below is an example: Assume that your plugin has a particular header image rendering function where it is safe to assume any image rendered with that function appears above the fold. You can rely on an arbitrary context specific to your function and then force WordPress core to consider images with that context to appear above the fold.
function force_myplugin_special_header_context_above_the_fold( $header_contexts ) {
$header_contexts['myplugin_special_header'] = true;
return $header_contexts;
}
add_filter(
'wp_loading_optimization_force_header_contexts',
'force_myplugin_special_header_context_above_the_fold'
);
Your image rendering function then needs to call wp_get_loading_optimization_attributes( 'img', $attr, 'myplugin_special_header' )
for the above filter to take effect.
Please refer to Trac ticket #58894 for additional details on these changes.
Hook priority change for wp_filter_content_tags()
The wp_filter_content_tags()
was originally introduced in WordPress 5.5 as the foundation for lazy-loading as well as other optimizations for certain tags in a content blob. More recently a problem was identified that the function is called earlier than do_shortcode()
, which means that any images added by shortcodes will not be able to make use of the performance benefits from wp_filter_content_tags()
.
To address that limitation, the hook priority with which wp_filter_content_tags()
is hooked into the various filters (“the_content”, “the_excerpt”, “widget_text_content”, and “widget_block_content”) has been changed from the default 10 to 12. For context, the do_shortcode()
function has hook priority 11.
While this is technically a breaking change, careful consideration and research across the WordPress plugin directory went into this decision. No plugins in the directory are affected by this change, given that there is only limited direct usage of the wp_filter_content_tags()
function, and such usage has not been specific to the core filters. Still, in case you use the wp_filter_content_tags()
function directly in your code, you may want to double check that this hook priority change does not result in a problem.
If your plugin processes custom content with the wp_filter_content_tags()
function, it is encouraged to call that function after parsing other content such as blocks and shortcodes. The core change will not cause problems if that is currently not the case in your code, however it is recommended to have your custom logic follow a similar order.
Please refer to Trac ticket #58853 for additional details on these changes.
Props to @westonruter and @webcommsat for review and proofreading.