WordPress.org

Make WordPress Core


Ignore:
Timestamp:
11/09/2021 12:34:17 AM (3 weeks ago)
Author:
flixos90
Message:

Media: Refine the heuristics to exclude certain images and iframes from being lazy-loaded to improve performance.

This changeset implements the refined lazy-loading behavior outlined in https://make.wordpress.org/core/2021/07/15/refining-wordpress-cores-lazy-loading-implementation/ in order to improve the Largest Contentful Paint metric, which can see a regression from images or iframes above the fold being lazy-loaded. Adjusting this so far has been possible for developers via filters and still is, however this enhancement brings a more accurate behavior out of the box for the majority of themes.

Specifically, this changeset skips the very first "content image or iframe" on the page from being lazy-loaded. "Content image or iframe" denotes any image or iframe that is found within content of any post in the current main query loop as well as any featured image of such a post. This applies both to "singular" as well as "archive" content: On a "singular" page the first image/iframe of the post is not lazy-loaded, while on an "archive" page the first image/iframe of the _first_ post in the query is not lazy-loaded.

This approach refines the lazy-loading behavior correctly for the majority of themes, which use a single-column layout for post content. For themes with multi-column layouts, a new wp_omit_loading_attr_threshold filter can be used to change how many of the first images/iframes are being skipped from lazy-loaded (default is 1). For example, a theme using a three-column grid of latest posts for archives could use the filter to override the threshold to 3 on archive pages, so that the first three content images/iframes would not be lazy-loaded.

Props adamsilverstein, azaozz, flixos90, hellofromtonya, jonoaldersonwp, mte90, rviscomi, tweetythierry, westonruter.
Fixes #53675. See #50425.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/media.php

    r52010 r52065  
    30253025     * @ticket 50425
    30263026     * @ticket 53463
     3027     * @ticket 53675
    30273028     * @dataProvider data_wp_lazy_loading_enabled_context_defaults
    30283029     *
     
    30473048            'get_avatar => true'              => array( 'get_avatar', true ),
    30483049            'arbitrary context => true'       => array( 'something_completely_arbitrary', true ),
     3050            'the_post_thumbnail => true'      => array( 'the_post_thumbnail', true ),
    30493051        );
    30503052    }
     
    31873189        );
    31883190    }
     3191
     3192    /**
     3193     * @ticket 53675
     3194     * @dataProvider data_wp_get_loading_attr_default
     3195     *
     3196     * @param string $context
     3197     */
     3198    function test_wp_get_loading_attr_default( $context ) {
     3199        global $wp_query, $wp_the_query;
     3200
     3201        // Return 'lazy' by default.
     3202        $this->assertSame( 'lazy', wp_get_loading_attr_default( 'test' ) );
     3203        $this->assertSame( 'lazy', wp_get_loading_attr_default( 'wp_get_attachment_image' ) );
     3204
     3205        // Return 'lazy' if not in the loop or the main query.
     3206        $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) );
     3207
     3208        $wp_query = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) );
     3209        $this->reset_content_media_count();
     3210        $this->reset_omit_loading_attr_filter();
     3211
     3212        while ( have_posts() ) {
     3213            the_post();
     3214
     3215            // Return 'lazy' if in the loop but not in the main query.
     3216            $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) );
     3217
     3218            // Set as main query.
     3219            $wp_the_query = $wp_query;
     3220
     3221            // For contexts other than for the main content, still return 'lazy' even in the loop
     3222            // and in the main query, and do not increase the content media count.
     3223            $this->assertSame( 'lazy', wp_get_loading_attr_default( 'wp_get_attachment_image' ) );
     3224
     3225            // Return `false` if in the loop and in the main query and it is the first element.
     3226            $this->assertFalse( wp_get_loading_attr_default( $context ) );
     3227
     3228            // Return 'lazy' if in the loop and in the main query for any subsequent elements.
     3229            $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) );
     3230
     3231            // Yes, for all subsequent elements.
     3232            $this->assertSame( 'lazy', wp_get_loading_attr_default( $context ) );
     3233        }
     3234    }
     3235
     3236    function data_wp_get_loading_attr_default() {
     3237        return array(
     3238            array( 'the_content' ),
     3239            array( 'the_post_thumbnail' ),
     3240        );
     3241    }
     3242
     3243    /**
     3244     * @ticket 53675
     3245     */
     3246    function test_wp_omit_loading_attr_threshold_filter() {
     3247        global $wp_query, $wp_the_query;
     3248
     3249        $wp_query     = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) );
     3250        $wp_the_query = $wp_query;
     3251        $this->reset_content_media_count();
     3252        $this->reset_omit_loading_attr_filter();
     3253
     3254        // Use the filter to alter the threshold for not lazy-loading to the first three elements.
     3255        add_filter(
     3256            'wp_omit_loading_attr_threshold',
     3257            function() {
     3258                return 3;
     3259            }
     3260        );
     3261
     3262        while ( have_posts() ) {
     3263            the_post();
     3264
     3265            // Due to the filter, now the first three elements should not be lazy-loaded, i.e. return `false`.
     3266            for ( $i = 0; $i < 3; $i++ ) {
     3267                $this->assertFalse( wp_get_loading_attr_default( 'the_content' ) );
     3268            }
     3269
     3270            // For following elements, lazy-load them again.
     3271            $this->assertSame( 'lazy', wp_get_loading_attr_default( 'the_content' ) );
     3272        }
     3273    }
     3274
     3275    /**
     3276     * @ticket 53675
     3277     */
     3278    function test_wp_filter_content_tags_with_wp_get_loading_attr_default() {
     3279        global $wp_query, $wp_the_query;
     3280
     3281        $img1         = get_image_tag( self::$large_id, '', '', '', 'large' );
     3282        $iframe1      = '<iframe src="https://www.example.com" width="640" height="360"></iframe>';
     3283        $img2         = get_image_tag( self::$large_id, '', '', '', 'medium' );
     3284        $img3         = get_image_tag( self::$large_id, '', '', '', 'thumbnail' );
     3285        $iframe2      = '<iframe src="https://wordpress.org" width="640" height="360"></iframe>';
     3286        $lazy_img2    = wp_img_tag_add_loading_attr( $img2, 'the_content' );
     3287        $lazy_img3    = wp_img_tag_add_loading_attr( $img3, 'the_content' );
     3288        $lazy_iframe2 = wp_iframe_tag_add_loading_attr( $iframe2, 'the_content' );
     3289
     3290        // Use a threshold of 2.
     3291        add_filter(
     3292            'wp_omit_loading_attr_threshold',
     3293            function() {
     3294                return 2;
     3295            }
     3296        );
     3297
     3298        // Following the threshold of 2, the first two content media elements should not be lazy-loaded.
     3299        $content_unfiltered = $img1 . $iframe1 . $img2 . $img3 . $iframe2;
     3300        $content_expected   = $img1 . $iframe1 . $lazy_img2 . $lazy_img3 . $lazy_iframe2;
     3301
     3302        $wp_query     = new WP_Query( array( 'post__in' => array( self::$post_ids['publish'] ) ) );
     3303        $wp_the_query = $wp_query;
     3304        $this->reset_content_media_count();
     3305        $this->reset_omit_loading_attr_filter();
     3306
     3307        while ( have_posts() ) {
     3308            the_post();
     3309
     3310            add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     3311            $content_filtered = wp_filter_content_tags( $content_unfiltered, 'the_content' );
     3312            remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' );
     3313        }
     3314
     3315        // After filtering, the first image should not be lazy-loaded while the other ones should be.
     3316        $this->assertSame( $content_expected, $content_filtered );
     3317    }
     3318
     3319    /**
     3320     * @ticket 53675
     3321     */
     3322    public function test_wp_omit_loading_attr_threshold() {
     3323        $this->reset_omit_loading_attr_filter();
     3324
     3325        // Apply filter, ensure default value of 1.
     3326        $omit_threshold = wp_omit_loading_attr_threshold();
     3327        $this->assertSame( 1, $omit_threshold );
     3328
     3329        // Add a filter that changes the value to 3. However, the filter is not applied a subsequent time in a single
     3330        // page load by default, so the value is still 1.
     3331        add_filter(
     3332            'wp_omit_loading_attr_threshold',
     3333            function() {
     3334                return 3;
     3335            }
     3336        );
     3337        $omit_threshold = wp_omit_loading_attr_threshold();
     3338        $this->assertSame( 1, $omit_threshold );
     3339
     3340        // Only by enforcing a fresh check, the filter gets re-applied.
     3341        $omit_threshold = wp_omit_loading_attr_threshold( true );
     3342        $this->assertSame( 3, $omit_threshold );
     3343    }
     3344
     3345    private function reset_content_media_count() {
     3346        // Get current value without increasing.
     3347        $content_media_count = wp_increase_content_media_count( 0 );
     3348
     3349        // Decrease it by its current value to "reset" it back to 0.
     3350        wp_increase_content_media_count( - $content_media_count );
     3351    }
     3352
     3353    private function reset_omit_loading_attr_filter() {
     3354        // Add filter to "reset" omit threshold back to null (unset).
     3355        add_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
     3356
     3357        // Force filter application to re-run.
     3358        wp_omit_loading_attr_threshold( true );
     3359
     3360        // Clean up the above filter.
     3361        remove_filter( 'wp_omit_loading_attr_threshold', '__return_null', 100 );
     3362    }
    31893363}
    31903364
Note: See TracChangeset for help on using the changeset viewer.