Skip to content

Instantly share code, notes, and snippets.

@mukeshpanchal27
Last active October 1, 2024 09:48
Show Gist options
  • Save mukeshpanchal27/bd0457cbfef993808beffe02a9389a8e to your computer and use it in GitHub Desktop.
Save mukeshpanchal27/bd0457cbfef993808beffe02a9389a8e to your computer and use it in GitHub Desktop.
POC: image sizes calculation using column context

WPP Column Context Plugin

Description

The WPP Column Context Plugin is a proof of concept (POC) WordPress plugin that demonstrates how to extend block context for core/columns and core/column blocks and dynamically calculate responsive image sizes based on column widths. The plugin adds custom context keys for columns and column widths, allowing block types like core/image to access this information when rendering.

Installation

  1. Download the plugin and place it in your WordPress wp-content/plugins/ directory.
  2. Activate the plugin through the WordPress admin dashboard.

Usage

  • Add a columns block (core/columns) and specify the column widths 50%.
  • Add an image block (core/image) inside the columns.
  • The plugin will automatically calculate and set the sizes attribute for the image, based on the column layout.

Example

For a simple 2-column layout, where each column is 50% wide:

  • The image block inside the column will have a sizes attribute calculated to match its column's width.
  • For an image aligned wide, the sizes attribute will be dynamically set to ensure optimal image loading.

Development Notes

  • The plugin uses a hardcoded example for wide alignment and statically checks for specific column layouts in wpp_better_sizes_calc. This is intended for demo purposes and can be extended for real-world use cases.
  • Performance improvements related to image metadata fetching still need to be addressed, as wp_calculate_image_sizes() currently makes a database query.

Credits

Inspiration for this plugin is taken from the current block binding implementation in WordPress Core:

<?php
/**
* Plugin Name: WPP Column Context
* Version: 0.1
* Author: Mukesh Panchal
* License: GPL2
*/
namespace WPP_Column_Context;
use WP_HTML_Tag_Processor;
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
add_filter(
'render_block_context',
function( $context, $block, $parent ) {
// Merge current context into the parent's context.
if ( $parent ) {
$context = array_merge( $parent->context, $context );
}
if ( 'core/columns' === $block['blockName'] ) {
$context['wpp-columns'] = count( $block['innerBlocks'] );
// Extra context for testing.
$context['wpp-columns-extra'] = 'columns-extra';
}
// For demo purposes, this assumes all unspecified columns are equal widths
if ( 'core/column' === $block['blockName'] ) {
$context['wpp-col-width'] = $block['attrs']['width'] ?? 'auto';
// Extra context for testing.
$context['wpp-col-extra'] = 'col-extra';
}
return $context;
},
10,
3
);
/*
* Inspiration taken from current block binding implementation.
* https://github.com/WordPress/wordpress-develop/blob/6.6/src/wp-includes/class-wp-block-bindings-registry.php#L193-L204
*/
add_filter(
'get_block_type_uses_context',
function ( $uses_context, $block_type ) {
if ( 'core/image' === $block_type->name ) {
// Use array_values to reset the array keys.
return array_values( array_unique( array_merge( $uses_context, array( 'wpp-columns','wpp-col-width' ) ) ) );
}
return $uses_context;
},
10,
2
);
add_filter( 'render_block_core/image', __NAMESPACE__ . '\\wpp_add_image_block_sizes', 10, 3 );
/**
* Calculate image sizes attribute for an image block during rendering.
*
* @param string $block_content The block content.
* @param array $parsed_block The parsed block data.
* @param WP_Block $wp_block The block instance.
* @return string The updated block content.
*/
function wpp_add_image_block_sizes( $block_content, $parsed_block, $wp_block ) {
// If you want to check the context for image block add `?debug` in you url.
if ( isset( $_GET['debug'] ) ) {
echo '<pre>';
print_r( $wp_block->context );
echo '</pre>';
}
/**
* Callback for calculating image sizes attribute value for an image block.
*
* This is a workaround to use block context data when calculating the img sizes attribute.
*
* @since 😎
*
* @param string $sizes The image sizes attribute value.
* @param array $size The image size data.
*/
$filter = function( $sizes, $size ) use ( $wp_block ) {
$alignment = $wp_block->attributes['align'] ?? '';
$columns = $wp_block->context['wpp-columns'] ?? ''; // Consider two columns
$column_width = $wp_block->context['wpp-col-width'] ?? ''; // Consider 50% each width
// Hypotehtical function to calculate better sizes.
$sizes = wpp_better_sizes_calc( $size, $alignment, $columns, $column_width );
return $sizes;
};
// Hook this filter early, before default fitlers are run.
add_filter( 'wp_calculate_image_sizes', $filter, 9, 2 );
/*
* A performance problem to still be solved with this approach is that
* wp_calculate_image_sizes() will make a DB query to get the image metadata.
* It does this to get the image width, which is needed to calculate
* the sizes attribute. Currently, this function is called from `wp_filter_content_tags`
* which has already primed the cache with the image metadata. That cache priming
* will need to be hanlded earlier if sizes is calculated during block rendering.
*/
$sizes = wp_calculate_image_sizes(
// If we don't have a size slug, assume the full size was used.
$parsed_block['attrs']['sizeSlug'] ?? 'full',
null,
null,
$parsed_block['attrs']['id'] ?? 0
);
remove_filter( 'wp_calculate_image_sizes', $filter, 9 );
// Bail early if sizes are not calculated.
if ( ! $sizes ) {
return $block_content;
}
// Add the sizes attribute to the image tag here.
$processor = new WP_HTML_Tag_Processor( $block_content );
$processor->next_tag( 'img' );
$processor->set_attribute( 'sizes', $sizes );
$return = $processor->get_updated_html();
return $return;
};
/**
* Hypothetical function to calculate better sizes.
*
* @param array $size The image size data.
* @param string $alignment The image alignment.
* @param int $columns The column count.
* @param string $column_width The column width.
* @return string The sizes attribute value.
*/
function wpp_better_sizes_calc( $size, $alignment, $columns, $column_width ) {
switch ( $alignment ) {
case 'full':
return '100vw';
break;
case 'wide':
// Hard coded wide size from TT4 theme for demo only.
if ( 2 === $columns && '50%' === $column_width ) { // Statically checking for column
return '(max-width: 640px) 100vw, 640px';
}
return '(max-width: 1280px) 100vw, 1280px';
break;
default:
// Hard coded wide size from TT4 theme for demo only.
if ( 2 === $columns && '50%' === $column_width ) { // Statically checking for column
return '(max-width: 310px) 100vw, 310px';
}
return '(max-width: 620px) 100vw, 620px';
}
}
@mukeshpanchal27
Copy link
Author

@felixarntz @joemcgill

If i use the filter without changes anything in current WP Core, I get the correct block_width_data context for image block.

$block_width_data = array();
add_filter(
	'render_block_context',
	function( $context, $block, $parent ) use ( &$block_width_data ) {
		if ( ! $parent ) {
			$block_width_data = array();
		}

		$block_width_data = $parent->context['block_width_data'] ?? $block_width_data;
		if ( 'core/group' === $block['blockName'] || 'core/columns' === $block['blockName'] ) {
			if ( isset( $block['attrs']['align'] ) ) {
				switch ( $block['attrs']['align'] ) {
					case 'full':
						$block_width_data[] = '100%';
						break;
					case 'wide':
						$block_width_data[] = '1400px'; // Use actual `wideWidth` here.
						break;
					default:
						$block_width_data[] = '800px'; // Use actual `contentWidth` here.
				}
			} else {
				$block_width_data[] = '800px'; // Use actual `contentWidth` here.
			}
		}
		if ( 'core/columns' === $block['blockName'] ) {
			// This is a special context key just to pass to the child 'core/column' block.
			$context['column_count'] = count( $block['innerBlocks'] );
		}
		if ( 'core/column' === $block['blockName'] ) {
			if ( isset( $block['attrs']['width'] ) && $block['attrs']['width'] ) {
				// Use specific column width.
				$block_width_data[] = $block['attrs']['width'];
			} elseif ( isset( $parent->context['column_count'] ) && $parent->context['column_count'] ) {
				// Determine the width based on equally sized columns.
				$block_width_data[] = '' . ( 100 / $parent->context['column_count'] ) . '%';
			}
		}
	
		if ( $block_width_data ) {
			$context['block_width_data'] = $block_width_data;
		}
		return $context;
	},
	10,
	3
);

The result:

If we have Group > Columns > Column ( 66% ) > Image then the result

[block_width_data] => Array
        (
            [0] => 800px // Group block default alignment
            [1] => 800px // Columns block default alignment
            [2] => 66.66%
        )

If we have Group > Columns > Column ( 66% ) > Columns > Column ( 50% ) > Image then the result

[block_width_data] => Array
        (
            [0] => 800px // Group block default alignment
            [1] => 800px // First Columns block default alignment
            [2] => 66.66%
            [3] => 800px // Second Columns block default alignment
            [4] => 50%
        )

If we have Group > Columns > Column ( 66% ) > Group > Columns > Column ( 50% ) > Image then the result

[block_width_data] => Array
        (
            [0] => 800px // Group block default alignment
            [1] => 800px // First Columns block default alignment
            [2] => 66.66%
            [3] => 800px // Second Group block default alignment
            [4] => 800px // Second Columns block default alignment
            [5] => 50%
        )

Notes: For the nested block the editor didn't allow the alignment option from third block. Group > Group > Columns in this case only the first and second Group block have alignment option not Columns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment