wp_get_loading_optimization_attributes

The timeline below displays how wordpress function wp_get_loading_optimization_attributes has changed across different WordPress versions. If a version is not listed, refer to the next available version below.

WordPress Version: 6.4

/**
 * Gets loading optimization attributes.
 *
 * This function returns an array of attributes that should be merged into the given attributes array to optimize
 * loading performance. Potential attributes returned by this function are:
 * - `loading` attribute with a value of "lazy"
 * - `fetchpriority` attribute with a value of "high"
 * - `decoding` attribute with a value of "async"
 *
 * If any of these attributes are already present in the given attributes, they will not be modified. Note that no
 * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case
 * both attributes are present with those values.
 *
 * @since 6.3.0
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string $tag_name The tag name.
 * @param array  $attr     Array of the attributes for the tag.
 * @param string $context  Context for the element for which the loading optimization attribute is requested.
 * @return array Loading optimization attributes.
 */
function wp_get_loading_optimization_attributes($tag_name, $attr, $context)
{
    global $wp_query;
    /**
     * Filters whether to short-circuit loading optimization attributes.
     *
     * Returning an array from the filter will effectively short-circuit the loading of optimization attributes,
     * returning that value instead.
     *
     * @since 6.4.0
     *
     * @param array|false $loading_attrs False by default, or array of loading optimization attributes to short-circuit.
     * @param string      $tag_name      The tag name.
     * @param array       $attr          Array of the attributes for the tag.
     * @param string      $context       Context for the element for which the loading optimization attribute is requested.
     */
    $loading_attrs = apply_filters('pre_wp_get_loading_optimization_attributes', false, $tag_name, $attr, $context);
    if (is_array($loading_attrs)) {
        return $loading_attrs;
    }
    $loading_attrs = array();
    /*
     * Skip lazy-loading for the overall block template, as it is handled more granularly.
     * The skip is also applicable for `fetchpriority`.
     */
    if ('template' === $context) {
        /** This filter is documented in wp-includes/media.php */
        return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
    }
    // For now this function only supports images and iframes.
    if ('img' !== $tag_name && 'iframe' !== $tag_name) {
        /** This filter is documented in wp-includes/media.php */
        return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
    }
    /*
     * Skip programmatically created images within content blobs as they need to be handled together with the other
     * images within the post content or widget content.
     * Without this clause, they would already be considered within their own context which skews the image count and
     * can result in the first post content image being lazy-loaded or an image further down the page being marked as a
     * high priority.
     */
    if ('the_content' !== $context && doing_filter('the_content') || 'widget_text_content' !== $context && doing_filter('widget_text_content') || 'widget_block_content' !== $context && doing_filter('widget_block_content')) {
        /** This filter is documented in wp-includes/media.php */
        return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
    }
    /*
     * Add `decoding` with a value of "async" for every image unless it has a
     * conflicting `decoding` attribute already present.
     */
    if ('img' === $tag_name) {
        if (isset($attr['decoding'])) {
            $loading_attrs['decoding'] = $attr['decoding'];
        } else {
            $loading_attrs['decoding'] = 'async';
        }
    }
    // For any resources, width and height must be provided, to avoid layout shifts.
    if (!isset($attr['width'], $attr['height'])) {
        /** This filter is documented in wp-includes/media.php */
        return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
    }
    /*
     * The key function logic starts here.
     */
    $maybe_in_viewport = null;
    $increase_count = false;
    $maybe_increase_count = false;
    // Logic to handle a `loading` attribute that is already provided.
    if (isset($attr['loading'])) {
        /*
         * Interpret "lazy" as not in viewport. Any other value can be
         * interpreted as in viewport (realistically only "eager" or `false`
         * to force-omit the attribute are other potential values).
         */
        if ('lazy' === $attr['loading']) {
            $maybe_in_viewport = false;
        } else {
            $maybe_in_viewport = true;
        }
    }
    // Logic to handle a `fetchpriority` attribute that is already provided.
    if (isset($attr['fetchpriority']) && 'high' === $attr['fetchpriority']) {
        /*
         * If the image was already determined to not be in the viewport (e.g.
         * from an already provided `loading` attribute), trigger a warning.
         * Otherwise, the value can be interpreted as in viewport, since only
         * the most important in-viewport image should have `fetchpriority` set
         * to "high".
         */
        if (false === $maybe_in_viewport) {
            _doing_it_wrong(__FUNCTION__, __('An image should not be lazy-loaded and marked as high priority at the same time.'), '6.3.0');
            /*
             * Set `fetchpriority` here for backward-compatibility as we should
             * not override what a developer decided, even though it seems
             * incorrect.
             */
            $loading_attrs['fetchpriority'] = 'high';
        } else {
            $maybe_in_viewport = true;
        }
    }
    if (null === $maybe_in_viewport) {
        $header_enforced_contexts = array('template_part_' . WP_TEMPLATE_PART_AREA_HEADER => true, 'get_header_image_tag' => true);
        /**
         * Filters the header-specific contexts.
         *
         * @since 6.4.0
         *
         * @param array $default_header_enforced_contexts Map of contexts for which elements should be considered
         *                                                in the header of the page, as $context => $enabled
         *                                                pairs. The $enabled should always be true.
         */
        $header_enforced_contexts = apply_filters('wp_loading_optimization_force_header_contexts', $header_enforced_contexts);
        // Consider elements with these header-specific contexts to be in viewport.
        if (isset($header_enforced_contexts[$context])) {
            $maybe_in_viewport = true;
            $maybe_increase_count = true;
        } elseif (!is_admin() && in_the_loop() && is_main_query()) {
            /*
             * Get the content media count, since this is a main query
             * content element. This is accomplished by "increasing"
             * the count by zero, as the only way to get the count is
             * to call this function.
             * The actual count increase happens further below, based
             * on the `$increase_count` flag set here.
             */
            $content_media_count = wp_increase_content_media_count(0);
            $increase_count = true;
            // If the count so far is below the threshold, `loading` attribute is omitted.
            if ($content_media_count < wp_omit_loading_attr_threshold()) {
                $maybe_in_viewport = true;
            } else {
                $maybe_in_viewport = false;
            }
        } elseif ($wp_query->before_loop && $wp_query->is_main_query() && did_action('get_header') && !did_action('get_footer')) {
            $maybe_in_viewport = true;
            $maybe_increase_count = true;
        }
    }
    /*
     * If the element is in the viewport (`true`), potentially add
     * `fetchpriority` with a value of "high". Otherwise, i.e. if the element
     * is not not in the viewport (`false`) or it is unknown (`null`), add
     * `loading` with a value of "lazy".
     */
    if ($maybe_in_viewport) {
        $loading_attrs = wp_maybe_add_fetchpriority_high_attr($loading_attrs, $tag_name, $attr);
    } else if (wp_lazy_loading_enabled($tag_name, $context)) {
        $loading_attrs['loading'] = 'lazy';
    }
    /*
     * If flag was set based on contextual logic above, increase the content
     * media count, either unconditionally, or based on whether the image size
     * is larger than the threshold.
     */
    if ($increase_count) {
        wp_increase_content_media_count();
    } elseif ($maybe_increase_count) {
        /** This filter is documented in wp-includes/media.php */
        $wp_min_priority_img_pixels = apply_filters('wp_min_priority_img_pixels', 50000);
        if ($wp_min_priority_img_pixels <= $attr['width'] * $attr['height']) {
            wp_increase_content_media_count();
        }
    }
    /**
     * Filters the loading optimization attributes.
     *
     * @since 6.4.0
     *
     * @param array  $loading_attrs The loading optimization attributes.
     * @param string $tag_name      The tag name.
     * @param array  $attr          Array of the attributes for the tag.
     * @param string $context       Context for the element for which the loading optimization attribute is requested.
     */
    return apply_filters('wp_get_loading_optimization_attributes', $loading_attrs, $tag_name, $attr, $context);
}

WordPress Version: 3.1

/**
 * Gets loading optimization attributes.
 *
 * This function returns an array of attributes that should be merged into the given attributes array to optimize
 * loading performance. Potential attributes returned by this function are:
 * - `loading` attribute with a value of "lazy"
 * - `fetchpriority` attribute with a value of "high"
 *
 * If any of these attributes are already present in the given attributes, they will not be modified. Note that no
 * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case
 * both attributes are present with those values.
 *
 * @since 6.3.0
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string $tag_name The tag name.
 * @param array  $attr     Array of the attributes for the tag.
 * @param string $context  Context for the element for which the loading optimization attribute is requested.
 * @return array Loading optimization attributes.
 */
function wp_get_loading_optimization_attributes($tag_name, $attr, $context)
{
    global $wp_query;
    /*
     * Closure for postprocessing logic.
     * It is here to avoid duplicate logic in many places below, without having
     * to introduce a very specific private global function.
     */
    $postprocess = static function ($loading_attributes, $with_fetchpriority = false) use ($tag_name, $attr, $context) {
        // Potentially add `fetchpriority="high"`.
        if ($with_fetchpriority) {
            $loading_attributes = wp_maybe_add_fetchpriority_high_attr($loading_attributes, $tag_name, $attr);
        }
        // Potentially strip `loading="lazy"` if the feature is disabled.
        if (isset($loading_attributes['loading']) && !wp_lazy_loading_enabled($tag_name, $context)) {
            unset($loading_attributes['loading']);
        }
        return $loading_attributes;
    };
    // Closure to increase media count for images with certain minimum threshold, mostly used for header images.
    $maybe_increase_content_media_count = static function () use ($attr) {
        /** This filter is documented in wp-includes/media.php */
        $wp_min_priority_img_pixels = apply_filters('wp_min_priority_img_pixels', 50000);
        // Images with a certain minimum size in the header of the page are also counted towards the threshold.
        if ($wp_min_priority_img_pixels <= $attr['width'] * $attr['height']) {
            wp_increase_content_media_count();
        }
    };
    $loading_attrs = array();
    /*
     * Skip lazy-loading for the overall block template, as it is handled more granularly.
     * The skip is also applicable for `fetchpriority`.
     */
    if ('template' === $context) {
        return $loading_attrs;
    }
    // For now this function only supports images and iframes.
    if ('img' !== $tag_name && 'iframe' !== $tag_name) {
        return $loading_attrs;
    }
    // For any resources, width and height must be provided, to avoid layout shifts.
    if (!isset($attr['width'], $attr['height'])) {
        return $loading_attrs;
    }
    if (isset($attr['loading'])) {
        /*
         * While any `loading` value could be set in `$loading_attrs`, for
         * consistency we only do it for `loading="lazy"` since that is the
         * only possible value that WordPress core would apply on its own.
         */
        if ('lazy' === $attr['loading']) {
            $loading_attrs['loading'] = 'lazy';
            if (isset($attr['fetchpriority']) && 'high' === $attr['fetchpriority']) {
                _doing_it_wrong(__FUNCTION__, __('An image should not be lazy-loaded and marked as high priority at the same time.'), '6.3.0');
            }
        }
        return $postprocess($loading_attrs, true);
    }
    // An image with `fetchpriority="high"` cannot be assigned `loading="lazy"` at the same time.
    if (isset($attr['fetchpriority']) && 'high' === $attr['fetchpriority']) {
        return $postprocess($loading_attrs, true);
    }
    /*
     * Do not lazy-load images in the header block template part, as they are likely above the fold.
     * For classic themes, this is handled in the condition below using the 'get_header' action.
     */
    $header_area = WP_TEMPLATE_PART_AREA_HEADER;
    if ("template_part_{$header_area}" === $context) {
        // Increase media count if there are images in header above a certian minimum size threshold.
        $maybe_increase_content_media_count();
        return $postprocess($loading_attrs, true);
    }
    // The custom header image is always expected to be in the header.
    if ('get_header_image_tag' === $context) {
        // Increase media count if there are images in header above a certian minimum size threshold.
        $maybe_increase_content_media_count();
        return $postprocess($loading_attrs, true);
    }
    // Special handling for programmatically created image tags.
    if ('the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context || 'widget_media_image' === $context) {
        /*
         * Skip programmatically created images within post content as they need to be handled together with the other
         * images within the post content.
         * Without this clause, they would already be considered below which skews the image count and can result in
         * the first post content image being lazy-loaded or an image further down the page being marked as a high
         * priority.
         */
        if (doing_filter('the_content')) {
            return $loading_attrs;
        }
        // Conditionally skip lazy-loading on images before the loop.
        if ($wp_query->before_loop && $wp_query->is_main_query() && did_action('get_header') && !did_action('get_footer')) {
            // Increase media count if there are images in header above a certian minimum size threshold.
            $maybe_increase_content_media_count();
            return $postprocess($loading_attrs, true);
        }
    }
    /*
     * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
     * as they are likely above the fold. Shortcodes are processed after content images, so if
     * thresholds haven't already been met, apply the same logic to those as well.
     */
    if ('the_content' === $context || 'the_post_thumbnail' === $context || 'do_shortcode' === $context) {
        // Only elements within the main query loop have special handling.
        if (is_admin() || !in_the_loop() || !is_main_query()) {
            $loading_attrs['loading'] = 'lazy';
            return $postprocess($loading_attrs, false);
        }
        // Increase the counter since this is a main query content element.
        $content_media_count = wp_increase_content_media_count();
        // If the count so far is below the threshold, `loading` attribute is omitted.
        if ($content_media_count <= wp_omit_loading_attr_threshold()) {
            // The first largest image will still get `fetchpriority='high'`.
            return $postprocess($loading_attrs, true);
        }
    }
    // Lazy-load by default for any unknown context.
    $loading_attrs['loading'] = 'lazy';
    return $postprocess($loading_attrs, false);
}

WordPress Version: 6.3

/**
 * Gets loading optimization attributes.
 *
 * This function returns an array of attributes that should be merged into the given attributes array to optimize
 * loading performance. Potential attributes returned by this function are:
 * - `loading` attribute with a value of "lazy"
 * - `fetchpriority` attribute with a value of "high"
 *
 * If any of these attributes are already present in the given attributes, they will not be modified. Note that no
 * element should have both `loading="lazy"` and `fetchpriority="high"`, so the function will trigger a warning in case
 * both attributes are present with those values.
 *
 * @since 6.3.0
 *
 * @global WP_Query $wp_query WordPress Query object.
 *
 * @param string $tag_name The tag name.
 * @param array  $attr     Array of the attributes for the tag.
 * @param string $context  Context for the element for which the loading optimization attribute is requested.
 * @return array Loading optimization attributes.
 */
function wp_get_loading_optimization_attributes($tag_name, $attr, $context)
{
    global $wp_query;
    /*
     * Closure for postprocessing logic.
     * It is here to avoid duplicate logic in many places below, without having
     * to introduce a very specific private global function.
     */
    $postprocess = static function ($loading_attributes, $with_fetchpriority = false) use ($tag_name, $attr, $context) {
        // Potentially add `fetchpriority="high"`.
        if ($with_fetchpriority) {
            $loading_attributes = wp_maybe_add_fetchpriority_high_attr($loading_attributes, $tag_name, $attr);
        }
        // Potentially strip `loading="lazy"` if the feature is disabled.
        if (isset($loading_attributes['loading']) && !wp_lazy_loading_enabled($tag_name, $context)) {
            unset($loading_attributes['loading']);
        }
        return $loading_attributes;
    };
    // Closure to increase media count for images with certain minimum threshold, mostly used for header images.
    $maybe_increase_content_media_count = static function () use ($attr) {
        /** This filter is documented in wp-admin/includes/media.php */
        $wp_min_priority_img_pixels = apply_filters('wp_min_priority_img_pixels', 50000);
        // Images with a certain minimum size in the header of the page are also counted towards the threshold.
        if ($wp_min_priority_img_pixels <= $attr['width'] * $attr['height']) {
            wp_increase_content_media_count();
        }
    };
    $loading_attrs = array();
    /*
     * Skip lazy-loading for the overall block template, as it is handled more granularly.
     * The skip is also applicable for `fetchpriority`.
     */
    if ('template' === $context) {
        return $loading_attrs;
    }
    // For now this function only supports images and iframes.
    if ('img' !== $tag_name && 'iframe' !== $tag_name) {
        return $loading_attrs;
    }
    // For any resources, width and height must be provided, to avoid layout shifts.
    if (!isset($attr['width'], $attr['height'])) {
        return $loading_attrs;
    }
    if (isset($attr['loading'])) {
        /*
         * While any `loading` value could be set in `$loading_attrs`, for
         * consistency we only do it for `loading="lazy"` since that is the
         * only possible value that WordPress core would apply on its own.
         */
        if ('lazy' === $attr['loading']) {
            $loading_attrs['loading'] = 'lazy';
            if (isset($attr['fetchpriority']) && 'high' === $attr['fetchpriority']) {
                _doing_it_wrong(__FUNCTION__, __('An image should not be lazy-loaded and marked as high priority at the same time.'), '6.3.0');
            }
        }
        return $postprocess($loading_attrs, true);
    }
    // An image with `fetchpriority="high"` cannot be assigned `loading="lazy"` at the same time.
    if (isset($attr['fetchpriority']) && 'high' === $attr['fetchpriority']) {
        return $postprocess($loading_attrs, true);
    }
    /*
     * Do not lazy-load images in the header block template part, as they are likely above the fold.
     * For classic themes, this is handled in the condition below using the 'get_header' action.
     */
    $header_area = WP_TEMPLATE_PART_AREA_HEADER;
    if ("template_part_{$header_area}" === $context) {
        // Increase media count if there are images in header above a certian minimum size threshold.
        $maybe_increase_content_media_count();
        return $postprocess($loading_attrs, true);
    }
    // The custom header image is always expected to be in the header.
    if ('get_header_image_tag' === $context) {
        // Increase media count if there are images in header above a certian minimum size threshold.
        $maybe_increase_content_media_count();
        return $postprocess($loading_attrs, true);
    }
    // Special handling for programmatically created image tags.
    if ('the_post_thumbnail' === $context || 'wp_get_attachment_image' === $context || 'widget_media_image' === $context) {
        /*
         * Skip programmatically created images within post content as they need to be handled together with the other
         * images within the post content.
         * Without this clause, they would already be considered below which skews the image count and can result in
         * the first post content image being lazy-loaded or an image further down the page being marked as a high
         * priority.
         */
        if (doing_filter('the_content')) {
            return $loading_attrs;
        }
        // Conditionally skip lazy-loading on images before the loop.
        if ($wp_query->before_loop && $wp_query->is_main_query() && did_action('get_header') && !did_action('get_footer')) {
            // Increase media count if there are images in header above a certian minimum size threshold.
            $maybe_increase_content_media_count();
            return $postprocess($loading_attrs, true);
        }
    }
    /*
     * The first elements in 'the_content' or 'the_post_thumbnail' should not be lazy-loaded,
     * as they are likely above the fold. Shortcodes are processed after content images, so if
     * thresholds haven't already been met, apply the same logic to those as well.
     */
    if ('the_content' === $context || 'the_post_thumbnail' === $context || 'do_shortcode' === $context) {
        // Only elements within the main query loop have special handling.
        if (is_admin() || !in_the_loop() || !is_main_query()) {
            $loading_attrs['loading'] = 'lazy';
            return $postprocess($loading_attrs, false);
        }
        // Increase the counter since this is a main query content element.
        $content_media_count = wp_increase_content_media_count();
        // If the count so far is below the threshold, `loading` attribute is omitted.
        if ($content_media_count <= wp_omit_loading_attr_threshold()) {
            // The first largest image will still get `fetchpriority='high'`.
            return $postprocess($loading_attrs, true);
        }
    }
    // Lazy-load by default for any unknown context.
    $loading_attrs['loading'] = 'lazy';
    return $postprocess($loading_attrs, false);
}