Make WordPress Core


Ignore:
Timestamp:
04/08/2020 12:53:18 AM (5 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.