Make WordPress Core


Ignore:
Timestamp:
04/08/2020 12:53:18 AM (6 years ago)
Author:
azaozz
Message:

Media: Enable lazy-loading of images by automatically adding the new loading="lazy" attribute to image tags on the front-end.

  • Introduces wp_lazy_loading_enabled(), wp_filter_content_tags(), wp_img_tag_add_loading_attr(), and wp_img_tag_add_srcset_and_sizes_attr() functions.
  • Introduces wp_lazy_loading_enabled, wp_img_tag_add_loading_attr, and wp_img_tag_add_srcset_and_sizes_attr filters.

Props flixos90, addyosmani, mor10, swissspidy, pierlo, westonruter, spacedmonkey, mikeschroder, jonoaldersonwp, peterwilsoncc, narwen, jeffpaul, OptimizingMatters, futtta, mukeshpanchal27, azaozz.

Fixes #44427.

File:
1 edited

Legend:

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

    r47394 r47554  
    10221022        $html  = '';
    10231023        $image = wp_get_attachment_image_src( $attachment_id, $size, $icon );
     1024
    10241025        if ( $image ) {
    10251026                list( $src, $width, $height ) = $image;
    1026                 $hwstring                     = image_hwstring( $width, $height );
    1027                 $size_class                   = $size;
     1027
     1028                $attachment = get_post( $attachment_id );
     1029                $hwstring   = image_hwstring( $width, $height );
     1030                $size_class = $size;
     1031
    10281032                if ( is_array( $size_class ) ) {
    10291033                        $size_class = join( 'x', $size_class );
    10301034                }
    1031                 $attachment   = get_post( $attachment_id );
     1035
    10321036                $default_attr = array(
    10331037                        'src'   => $src,
     
    10361040                );
    10371041
     1042                // Add `loading` attribute.
     1043                if ( wp_lazy_loading_enabled( 'img', 'wp_get_attachment_image' ) ) {
     1044                        $default_attr['loading'] = 'lazy';
     1045                }
     1046
    10381047                $attr = wp_parse_args( $attr, $default_attr );
    10391048
     
    10621071                 * @since 2.8.0
    10631072                 *
    1064                  * @param string[]     $attr       Array of attribute values for the image markup, keyed by attribute name.
     1073                 * @param array        $attr       Array of attribute values for the image markup, keyed by attribute name.
    10651074                 *                                 See wp_get_attachment_image().
    10661075                 * @param WP_Post      $attachment Image attachment post.
     
    10691078                 */
    10701079                $attr = apply_filters( 'wp_get_attachment_image_attributes', $attr, $attachment, $size );
     1080
    10711081                $attr = array_map( 'esc_attr', $attr );
    10721082                $html = rtrim( "<img $hwstring" );
     1083
    10731084                foreach ( $attr as $name => $value ) {
    10741085                        $html .= " $name=" . '"' . $value . '"';
    10751086                }
     1087
    10761088                $html .= ' />';
    10771089        }
     
    14791491
    14801492/**
    1481  * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes.
    1482  *
    1483  * @since 4.4.0
    1484  *
    1485  * @see wp_image_add_srcset_and_sizes()
    1486  *
    1487  * @param string $content The raw post content to be filtered.
    1488  * @return string Converted content with 'srcset' and 'sizes' attributes added to images.
    1489  */
    1490 function wp_make_content_images_responsive( $content ) {
    1491         if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) ) {
    1492                 return $content;
    1493         }
    1494 
    1495         $selected_images = array();
    1496         $attachment_ids  = array();
    1497 
    1498         foreach ( $matches[0] as $image ) {
    1499                 if ( false === strpos( $image, ' srcset=' ) && preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) {
    1500                         $attachment_id = absint( $class_id[1] );
    1501 
    1502                         if ( $attachment_id ) {
    1503                                 /*
    1504                                  * If exactly the same image tag is used more than once, overwrite it.
    1505                                  * All identical tags will be replaced later with 'str_replace()'.
    1506                                  */
    1507                                 $selected_images[ $image ] = $attachment_id;
    1508                                 // Overwrite the ID when the same image is included more than once.
    1509                                 $attachment_ids[ $attachment_id ] = true;
    1510                         }
    1511                 }
    1512         }
    1513 
    1514         if ( count( $attachment_ids ) > 1 ) {
    1515                 /*
    1516                  * Warm the object cache with post and meta information for all found
    1517                  * images to avoid making individual database calls.
    1518                  */
    1519                 _prime_post_caches( array_keys( $attachment_ids ), false, true );
    1520         }
    1521 
    1522         foreach ( $selected_images as $image => $attachment_id ) {
    1523                 $image_meta = wp_get_attachment_metadata( $attachment_id );
    1524                 $content    = str_replace( $image, wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ), $content );
    1525         }
    1526 
    1527         return $content;
    1528 }
    1529 
    1530 /**
    15311493 * Adds 'srcset' and 'sizes' attributes to an existing 'img' element.
    15321494 *
     
    16121574                // Add 'srcset' and 'sizes' attributes to the image markup.
    16131575                $image = preg_replace( '/<img ([^>]+?)[\/ ]*>/', '<img $1' . $attr . ' />', $image );
     1576        }
     1577
     1578        return $image;
     1579}
     1580
     1581/**
     1582 * Determine whether to add the `loading` attribute to the specified tag in the specified context.
     1583 *
     1584 * @since 5.5.0
     1585 *
     1586 * @param string $tag_name The tag name.
     1587 * @param string $context Additional context, like the current filter name or the function name from where this was called.
     1588 * @return bool Whether to add the attribute.
     1589 */
     1590function wp_lazy_loading_enabled( $tag_name, $context ) {
     1591        // By default add to all 'img' tags.
     1592        // See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading
     1593        $default = ( 'img' === $tag_name );
     1594
     1595        /**
     1596         * Filters whether to add the `loading` attribute to the specified tag in the specified context.
     1597         *
     1598         * @since 5.5.0
     1599         *
     1600         * @param bool   $default Default value.
     1601         * @param string $tag_name The tag name.
     1602         * @param string $context Additional context, like the current filter name or the function name from where this was called.
     1603         */
     1604        return (bool) apply_filters( 'wp_lazy_loading_enabled', $default, $tag_name, $context );
     1605}
     1606
     1607/**
     1608 * Filters specific tags in post content and modifies their markup.
     1609 *
     1610 * This function adds `srcset`, `sizes`, and `loading` attributes to `img` HTML tags.
     1611 *
     1612 * @since 5.5.0
     1613 *
     1614 * @see wp_img_tag_add_loading_attr()
     1615 * @see wp_img_tag_add_srcset_and_sizes_attr()
     1616 *
     1617 * @param string $content The HTML content to be filtered.
     1618 * @param string $context Optional. Additional context to pass to the filters. Defaults to `current_filter()` when not set.
     1619 * @return string Converted content with images modified.
     1620 */
     1621function wp_filter_content_tags( $content, $context = null ) {
     1622        if ( null === $context ) {
     1623                $context = current_filter();
     1624        }
     1625
     1626        $add_loading_attr = wp_lazy_loading_enabled( 'img', $context );
     1627
     1628        if ( false === strpos( $content, '<img' ) ) {
     1629                return $content;
     1630        }
     1631
     1632        if ( ! preg_match_all( '/<img\s[^>]+>/', $content, $matches ) ) {
     1633                return $content;
     1634        }
     1635
     1636        // List of the unique `img` tags found in $content.
     1637        $images = array();
     1638
     1639        foreach ( $matches[0] as $image ) {
     1640                if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) {
     1641                        $attachment_id = absint( $class_id[1] );
     1642
     1643                        if ( $attachment_id ) {
     1644                                // If exactly the same image tag is used more than once, overwrite it.
     1645                                // All identical tags will be replaced later with 'str_replace()'.
     1646                                $images[ $image ] = $attachment_id;
     1647                                continue;
     1648                        }
     1649                }
     1650
     1651                $images[ $image ] = 0;
     1652        }
     1653
     1654        // Reduce the array to unique attachment IDs.
     1655        $attachment_ids = array_unique( array_filter( array_values( $images ) ) );
     1656
     1657        if ( count( $attachment_ids ) > 1 ) {
     1658                /*
     1659                 * Warm the object cache with post and meta information for all found
     1660                 * images to avoid making individual database calls.
     1661                 */
     1662                _prime_post_caches( $attachment_ids, false, true );
     1663        }
     1664
     1665        foreach ( $images as $image => $attachment_id ) {
     1666                $filtered_image = $image;
     1667
     1668                // Add 'srcset' and 'sizes' attributes if applicable.
     1669                if ( $attachment_id > 0 && false === strpos( $filtered_image, ' srcset=' ) ) {
     1670                        $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id );
     1671                }
     1672
     1673                // Add 'loading' attribute if applicable.
     1674                if ( $add_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) {
     1675                        $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context );
     1676                }
     1677
     1678                if ( $filtered_image !== $image ) {
     1679                        $content = str_replace( $image, $filtered_image, $content );
     1680                }
     1681        }
     1682
     1683        return $content;
     1684}
     1685
     1686/**
     1687 * Adds `loading` attribute to an `img` HTML tag.
     1688 *
     1689 * @since 5.5.0
     1690 *
     1691 * @param string $image   The HTML `img` tag where the attribute should be added.
     1692 * @param string $context Additional context to pass to the filters.
     1693 * @return string Converted `img` tag with `loading` attribute added.
     1694 */
     1695function wp_img_tag_add_loading_attr( $image, $context ) {
     1696        /**
     1697         * Filters the `loading` attribute value. Default `lazy`.
     1698         *
     1699         * Returning `false` or an empty string will not add the attribute.
     1700         * Returning `true` will add the default value.
     1701         *
     1702         * @since 5.5.0
     1703         *
     1704         * @param string $value   The `loading` attribute value, defaults to `lazy`.
     1705         * @param string $image   The HTML `img` tag to be filtered.
     1706         * @param string $context Additional context about how the function was called or where the img tag is.
     1707         */
     1708        $value = apply_filters( 'wp_img_tag_add_loading_attr', 'lazy', $image, $context );
     1709
     1710        if ( $value ) {
     1711                if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
     1712                        $value = 'lazy';
     1713                }
     1714
     1715                $quote = null;
     1716
     1717                // Check if the img tag is valid (has `src` attribute) and get the quote character.
     1718                // In almost all cases it will have src and a double quote.
     1719                if ( false !== strpos( $image, ' src="' ) ) {
     1720                        $quote = '"';
     1721                } elseif ( preg_match( '/\ssrc\s*=(["\'])/', $image, $matches ) ) {
     1722                        $quote = $matches[1];
     1723                }
     1724
     1725                if ( $quote ) {
     1726                        $loading = "loading={$quote}{$value}{$quote}";
     1727
     1728                        return str_replace( '<img', "<img {$loading}", $image );
     1729                }
     1730        }
     1731
     1732        return $image;
     1733}
     1734
     1735/**
     1736 * Adds `srcset` and `sizes` attributes to an existing `img` HTML tag.
     1737 *
     1738 * @since 5.5.0
     1739 *
     1740 * @param string $image         The HTML `img` tag where the attribute should be added.
     1741 * @param string $context       Additional context to pass to the filters.
     1742 * @param int    $attachment_id Image attachment ID.
     1743 * @return string Converted 'img' element with 'loading' attribute added.
     1744 */
     1745function wp_img_tag_add_srcset_and_sizes_attr( $image, $context, $attachment_id ) {
     1746        /**
     1747         * Filters whether to add the `srcset` and `sizes` HTML attributes to the img tag. Default `true`.
     1748         *
     1749         * Returning anything else than `true` will not add the attributes.
     1750         *
     1751         * @since 5.5.0
     1752         *
     1753         * @param bool   $value         The filtered value, defaults to `true`.
     1754         * @param string $image         The HTML `img` tag where the attribute should be added.
     1755         * @param string $context       Additional context about how the function was called or where the img tag is.
     1756         * @param int    $attachment_id The image attachment ID.
     1757         */
     1758        $add = apply_filters( 'wp_img_tag_add_srcset_and_sizes_attr', true, $image, $context, $attachment_id );
     1759
     1760        if ( true === $add ) {
     1761                $image_meta = wp_get_attachment_metadata( $attachment_id );
     1762                return wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id );
    16141763        }
    16151764
Note: See TracChangeset for help on using the changeset viewer.