Skip to content

Instantly share code, notes, and snippets.

@westonruter
Last active September 12, 2023 18:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save westonruter/456afa3cbf29563ffd590401988f1a23 to your computer and use it in GitHub Desktop.
Save westonruter/456afa3cbf29563ffd590401988f1a23 to your computer and use it in GitHub Desktop.
<?php
/**
* Optimize Content Hero Image WordPress Plugin.
*
* @package OptimizeContentHeroImage
* @author Weston Ruter, Google
* @license GPL-2.0-or-later
* @copyright 2023 Google Inc.
*
* @wordpress-plugin
* Plugin Name: Optimize Content Hero Image
* Description: When using a non-standard template system (e.g. Timber), the logic in <code>wp_get_loading_optimization_attributes()</code> may not work properly. This ensures that the first content images are not lazy-loaded and the largest is given <code>fetchpriority=high</code>. See <a href="https://core.trac.wordpress.org/ticket/59331">#59331</a> for how this might be fixed in WordPress Core.
* Plugin URI: https://gist.github.com/westonruter/456afa3cbf29563ffd590401988f1a23
* Version: 0.1.1
* Author: Weston Ruter
* Author URI: https://weston.ruter.net/
* License: GNU General Public License v2 (or later)
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
* Update URI: https://gist.github.com/westonruter/456afa3cbf29563ffd590401988f1a23
*/
namespace OptimizeContentHeroImage;
use WP_HTML_Tag_Processor;
const FILTER_PRIORITY = 100;
/**
* Optimize content images.
*
* @param string $content String
* @return string Optimized content.
*/
function optimize_content_images( string $content ): string {
// Ensure only the first post is processed.
remove_filter( 'the_content', __NAMESPACE__ . '\optimize_content_images', FILTER_PRIORITY );
$p = new WP_HTML_Tag_Processor( $content );
$tag_counts = [];
$hero_candidate_count = 0;
$hero_candidates = [];
while ( $p->next_tag() ) {
$tag_name = $p->get_tag();
if ( ! isset( $tag_counts[ $tag_name ] ) ) {
$tag_counts[ $tag_name ] = 1;
} else {
$tag_counts[ $tag_name ]++;
}
// Once the second paragraph is encountered, stop optimizing images.
if ( 'P' === $tag_name && $tag_counts[ $tag_name ] > 1 ) {
break;
}
if ( $tag_name === 'IMG' ) {
// Stop optimizing after the 5th image.
if ( $tag_counts[ $tag_name ] > 5 ) {
break;
}
// Prevent lazy-loading.
$p->remove_attribute( 'loading' );
// Add fetchpriority=high if hero.
$width = (int) $p->get_attribute( 'width' );
$height = (int) $p->get_attribute( 'height' );
$size = $width * $height;
if ( $size >= 50000 ) { // Number comes from wp_min_priority_img_pixels in core.
$hero_candidate_count++;
$bookmark_name = "hero_candidate_{$hero_candidate_count}";
$p->set_bookmark( $bookmark_name );
$hero_candidates[ $bookmark_name ] = $size;
}
}
}
// Add fetchpriority=high to the largest hero candidate.
if ( count( $hero_candidates ) > 0 ) {
arsort( $hero_candidates, SORT_NUMERIC );
$bookmark_name = key( $hero_candidates );
$p->seek( $bookmark_name );
$p->set_attribute( 'fetchpriority', 'high' );
}
return $p->get_updated_html();
}
add_filter( 'the_content', __NAMESPACE__ . '\optimize_content_images', FILTER_PRIORITY );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment