Make WordPress Core


Ignore:
Timestamp:
06/26/2023 04:15:12 PM (23 months ago)
Author:
flixos90
Message:

Media: Automatically add fetchpriority="high" to hero image to improve load time performance.

This changeset adds support for the fetchpriority attribute, which is typically added to a single image in each HTML response with a value of "high". This enhances load time performance (also Largest Contentful Paint, or LCP) by telling the browser to prioritize this image for downloading even before the layout of the page has been computed. In lab tests, this has shown to improve LCP performance by ~10% on average.

Specifically, fetchpriority="high" is added to the first image that satisfies all of the following conditions:

  • The image is not lazy-loaded, i.e. does not have loading="lazy".
  • The image does not already have a (conflicting) fetchpriority attribute.
  • The size of of the image (i.e. width * height) is greater than 50,000 squarepixels.

While these heuristics are based on several field analyses, there will always be room for optimization. Sites can customize the squarepixel threshold using a new filter wp_min_priority_img_pixels which should return an integer for the value.

Since the logic for adding fetchpriority="high" is heavily intertwined with the logic for adding loading="lazy", yet the features should work decoupled from each other, the majority of code changes in this changeset is refactoring of the existing lazy-loading logic to be reusable. For this purpose, a new function wp_get_loading_optimization_attributes() has been introduced which returns an associative array of performance-relevant attributes for a given HTML element. This function replaces wp_get_loading_attr_default(), which has been deprecated. As another result of that change, a new function wp_img_tag_add_loading_optimization_attrs() replaces the more specific wp_img_tag_add_loading_attr(), which has been deprecated as well.

See https://make.wordpress.org/core/2023/05/02/proposal-for-enhancing-lcp-image-performance-with-fetchpriority/ for the original proposal and additional context.

Props thekt12, joemcgill, spacedmonkey, mukesh27, costdev, 10upsimon.
Fixes #58235.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/deprecated.php

    r56020 r56037  
    46594659    wp_lazyload_comment_meta( $comment_ids );
    46604660}
     4661
     4662/**
     4663 * Gets the default value to use for a `loading` attribute on an element.
     4664 *
     4665 * This function should only be called for a tag and context if lazy-loading is generally enabled.
     4666 *
     4667 * The function usually returns 'lazy', but uses certain heuristics to guess whether the current element is likely to
     4668 * appear above the fold, in which case it returns a boolean `false`, which will lead to the `loading` attribute being
     4669 * omitted on the element. The purpose of this refinement is to avoid lazy-loading elements that are within the initial
     4670 * viewport, which can have a negative performance impact.
     4671 *
     4672 * Under the hood, the function uses {@see wp_increase_content_media_count()} every time it is called for an element
     4673 * within the main content. If the element is the very first content element, the `loading` attribute will be omitted.
     4674 * This default threshold of 3 content elements to omit the `loading` attribute for can be customized using the
     4675 * {@see 'wp_omit_loading_attr_threshold'} filter.
     4676 *
     4677 * @since 5.9.0
     4678 * @deprecated 6.3.0 Use wp_get_loading_optimization_attributes() instead.
     4679 * @see wp_get_loading_optimization_attributes()
     4680 *
     4681 * @global WP_Query $wp_query WordPress Query object.
     4682 *
     4683 * @param string $context Context for the element for which the `loading` attribute value is requested.
     4684 * @return string|bool The default `loading` attribute value. Either 'lazy', 'eager', or a boolean `false`, to indicate
     4685 *                     that the `loading` attribute should be skipped.
     4686 */
     4687function wp_get_loading_attr_default( $context ) {
     4688    _deprecated_function( __FUNCTION__, '6.3.0', 'wp_get_loading_optimization_attributes' );
     4689    global $wp_query;
     4690
     4691    // Skip lazy-loading for the overall block template, as it is handled more granularly.
     4692    if ( 'template' === $context ) {
     4693        return false;
     4694    }
     4695
     4696    /*
     4697     * Do not lazy-load images in the header block template part, as they are likely above the fold.
     4698     * For classic themes, this is handled in the condition below using the 'get_header' action.
     4699     */
     4700    $header_area = WP_TEMPLATE_PART_AREA_HEADER;
     4701    if ( "template_part_{$header_area}" === $context ) {
     4702        return false;
     4703    }
     4704
     4705    // Special handling for programmatically created image tags.
     4706    if ( 'the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context ) {
     4707        /*
     4708         * Skip programmatically created images within post content as they need to be handled together with the other
     4709         * images within the post content.
     4710         * Without this clause, they would already be counted below which skews the number and can result in the first
     4711         * post content image being lazy-loaded only because there are images elsewhere in the post content.
     4712         */
     4713        if ( doing_filter( 'the_content' ) ) {
     4714            return false;
     4715        }
     4716
     4717        // Conditionally skip lazy-loading on images before the loop.
     4718        if (
     4719            // Only apply for main query but before the loop.
     4720            $wp_query->before_loop && $wp_query->is_main_query()
     4721            /*
     4722             * Any image before the loop, but after the header has started should not be lazy-loaded,
     4723             * except when the footer has already started which can happen when the current template
     4724             * does not include any loop.
     4725             */
     4726            && did_action( 'get_header' ) && ! did_action( 'get_footer' )
     4727        ) {
     4728            return false;
     4729        }
     4730    }
     4731
     4732    /*
     4733     * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
     4734     * as they are likely above the fold.
     4735     */
     4736    if ( 'the_content' === $context || 'the_post_thumbnail' === $context ) {
     4737        // Only elements within the main query loop have special handling.
     4738        if ( is_admin() || ! in_the_loop() || ! is_main_query() ) {
     4739            return 'lazy';
     4740        }
     4741
     4742        // Increase the counter since this is a main query content element.
     4743        $content_media_count = wp_increase_content_media_count();
     4744
     4745        // If the count so far is below the threshold, return `false` so that the `loading` attribute is omitted.
     4746        if ( $content_media_count <= wp_omit_loading_attr_threshold() ) {
     4747            return false;
     4748        }
     4749
     4750        // For elements after the threshold, lazy-load them as usual.
     4751        return 'lazy';
     4752    }
     4753
     4754    // Lazy-load by default for any unknown context.
     4755    return 'lazy';
     4756}
     4757
     4758/**
     4759 * Adds `loading` attribute to an `img` HTML tag.
     4760 *
     4761 * @since 5.5.0
     4762 * @deprecated 6.3.0 Use wp_img_tag_add_loading_optimization_attrs() instead.
     4763 * @see wp_img_tag_add_loading_optimization_attrs()
     4764 *
     4765 * @param string $image   The HTML `img` tag where the attribute should be added.
     4766 * @param string $context Additional context to pass to the filters.
     4767 * @return string Converted `img` tag with `loading` attribute added.
     4768 */
     4769function wp_img_tag_add_loading_attr( $image, $context ) {
     4770    _deprecated_function( __FUNCTION__, '6.3.0', 'wp_img_tag_add_loading_optimization_attrs' );
     4771    /*
     4772     * Get loading attribute value to use. This must occur before the conditional check below so that even images that
     4773     * are ineligible for being lazy-loaded are considered.
     4774     */
     4775    $value = wp_get_loading_attr_default( $context );
     4776
     4777    // Images should have source and dimension attributes for the `loading` attribute to be added.
     4778    if ( ! str_contains( $image, ' src="' ) || ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) {
     4779        return $image;
     4780    }
     4781
     4782    /**
     4783     * Filters the `loading` attribute value to add to an image. Default `lazy`.
     4784     *
     4785     * Returning `false` or an empty string will not add the attribute.
     4786     * Returning `true` will add the default value.
     4787     *
     4788     * @since 5.5.0
     4789     *
     4790     * @param string|bool $value   The `loading` attribute value. Returning a falsey value will result in
     4791     *                             the attribute being omitted for the image.
     4792     * @param string      $image   The HTML `img` tag to be filtered.
     4793     * @param string      $context Additional context about how the function was called or where the img tag is.
     4794     */
     4795    $value = apply_filters( 'wp_img_tag_add_loading_attr', $value, $image, $context );
     4796
     4797    if ( $value ) {
     4798        if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
     4799            $value = 'lazy';
     4800        }
     4801
     4802        return str_replace( '<img', '<img loading="' . esc_attr( $value ) . '"', $image );
     4803    }
     4804
     4805    return $image;
     4806}
Note: See TracChangeset for help on using the changeset viewer.