wp_get_loading_optimization_attributes( string $tag_name, array $attr, string $context ): array

Gets loading optimization attributes.

Description

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.

Parameters

$tag_namestringrequired
The tag name.
$attrarrayrequired
Array of the attributes for the tag.
$contextstringrequired
Context for the element for which the loading optimization attribute is requested.

Return

array Loading optimization attributes.

Source

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 (
			// Only apply for main query but before the loop.
			$wp_query->before_loop && $wp_query->is_main_query()
			/*
			 * Any image before the loop, but after the header has started should not be lazy-loaded,
			 * except when the footer has already started which can happen when the current template
			 * does not include any loop.
			 */
			&& 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 {
		// Only add `loading="lazy"` if the feature is enabled.
		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 );
}

Hooks

apply_filters( ‘pre_wp_get_loading_optimization_attributes’, array|false $loading_attrs, string $tag_name, array $attr, string $context )

Filters whether to short-circuit loading optimization attributes.

apply_filters( ‘wp_get_loading_optimization_attributes’, array $loading_attrs, string $tag_name, array $attr, string $context )

Filters the loading optimization attributes.

apply_filters( ‘wp_loading_optimization_force_header_contexts’, array $default_header_enforced_contexts )

Filters the header-specific contexts.

apply_filters( ‘wp_min_priority_img_pixels’, int $threshold )

Filters the minimum square-pixels threshold for an image to be eligible as the high-priority image.

Changelog

VersionDescription
6.3.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.