[wp-trac] [WordPress Trac] #45407: Add block attributes to `wp_calculate_image_sizes` to allow for proper handling of `sizes` attribute

WordPress Trac noreply at wordpress.org
Mon Mar 9 08:23:17 UTC 2026


#45407: Add block attributes to `wp_calculate_image_sizes` to allow for proper
handling of `sizes` attribute
--------------------------------------+-----------------------------
 Reporter:  mor10                     |       Owner:  joemcgill
     Type:  defect (bug)              |      Status:  assigned
 Priority:  high                      |   Milestone:  Future Release
Component:  Media                     |     Version:
 Severity:  major                     |  Resolution:
 Keywords:  dev-feedback needs-patch  |     Focuses:
--------------------------------------+-----------------------------

Comment (by sanket.parmar):

 == Proposed fix: inject correct `sizes` in `render_block_core_image()`

 === Where should this fix land?

 This fix should follow the standard Gutenberg → Core merge flow:

  1. '''Primary: Gutenberg plugin''' (`packages/block-
 library/src/image/index.php`)[[BR]]
     Gutenberg ships on its own release cycle (every two weeks) and reaches
 sites running the plugin immediately. This is where the change should be
 authored and reviewed first, as a Gutenberg PR.

  2. '''Secondary: WordPress Core''' (`src/wp-
 includes/blocks/image.php`)[[BR]]
     `src/wp-includes/blocks/image.php` in `wordpress-develop` is a
 '''build artifact''' — it is generated from the Gutenberg package during
 the build and is listed in `.gitignore` under `/src/wp-includes/blocks/*`.
 It should '''not''' be patched directly. Once the Gutenberg PR is merged,
 the file gets synced into Core automatically as part of the regular
 Gutenberg → Core merge that happens each WordPress release cycle.

 In short: '''open the PR against the Gutenberg repository'''. The Core
 ticket (#45407) should reference that PR and be resolved when the sync
 lands in a Core release.

 ----

 === Why the original approach stalled

 The `45407.diff` patch required the image block to be server-rendered so
 block attributes could be passed into `wp_calculate_image_sizes()`. At the
 time (WP 5.0), `core/image` was client-rendered. '''That blocker no longer
 exists.''' Since WP 5.9, `core/image` has `render_block_core_image()` as a
 registered `render_callback`, giving us full access to
 `$attributes['align']` at render time.

 === Proposed fix

 Two additions to `packages/block-library/src/image/index.php`.

 The later `wp_filter_content_tags()` pass (priority 12 on `the_content`)
 does not override a pre-existing `sizes` attribute —
 `wp_image_add_srcset_and_sizes()` already contains this guard:

 {{{
 #!php
 // Check if there is already a 'sizes' attribute.
 $sizes = strpos( $image, ' sizes=' );
 if ( ! $sizes ) {
     $sizes = wp_calculate_image_sizes( ... );
 }
 }}}

 So setting `sizes` earlier in the render callback is safe and will not be
 double-applied.

 ----

 === 1. New helper function — add before `render_block_core_image()`

 {{{
 #!php
 /**
  * Calculates a correct `sizes` attribute for a core/image block based on
 its alignment.
  *
  * The generic wp_filter_content_tags() pipeline sets sizes based on the
 registered
  * image size width, which is correct for default/center/left/right
 aligned images but
  * is too narrow for alignwide and alignfull images that visually break
 out of the
  * content column. This function produces a sizes value that matches the
 actual display
  * width of the image in those contexts.
  *
  * Themes and plugins can override the result via the {@see
 'block_core_image_sizes'}
  * filter hook.
  *
  * @see https://core.trac.wordpress.org/ticket/45407
  *
  * @global int $content_width The theme's maximum content width in pixels.
  *
  * @param string $align       Block alignment value: 'wide', 'full',
 'center',
  *                             'left', 'right', or '' for no alignment.
  * @param int    $image_width The intrinsic width of the image in pixels
 as stored
  *                             in the block's markup.
  * @return string The `sizes` attribute value to inject, or an empty
 string when the
  *                default core behaviour should be preserved.
  */
 function block_core_image_calculate_sizes( $align, $image_width ) {
     global $content_width;

     switch ( $align ) {
         case 'full':
             /*
              * Full-width images span the entire viewport width regardless
 of the
              * theme's layout, so 100vw is always the correct hint.
              */
             $sizes = '100vw';
             break;

         case 'wide':
             /*
              * Wide-aligned images should be displayed at the theme's wide
 layout
              * width. Prefer the pixel value declared in theme.json's
 wideSize;
              * fall back to a 130% multiple of the classic-theme
 $content_width
              * global as a reasonable heuristic.
              */
             $layout        = wp_get_global_settings( array( 'layout' ) );
             $wide_size_raw = isset( $layout['wideSize'] ) ?
 $layout['wideSize'] : '';
             $wide_px       = 0;

             if ( $wide_size_raw ) {
                 // Extract the largest px value (handles 'clamp(…)'
 expressions too).
                 preg_match_all( '/(\d+)px/', $wide_size_raw, $px_matches
 );
                 if ( ! empty( $px_matches[1] ) ) {
                     $wide_px = (int) max( $px_matches[1] );
                 }
             }

             if ( ! $wide_px && ! empty( $content_width ) ) {
                 $wide_px = (int) round( $content_width * 1.3 );
             }

             $sizes = $wide_px
                 ? sprintf( '(max-width: %1$dpx) 100vw, %1$dpx', $wide_px )
                 : '100vw';
             break;

         default:
             /*
              * For center, left, right, or no alignment the default core
 behaviour
              * (sizing based on the rendered image width) is already
 correct.
              * Return an empty string to signal that no override is
 needed.
              */
             return '';
     }

     /**
      * Filters the `sizes` attribute computed for a core/image block.
      *
      * Themes and plugins can return a custom value that matches their own
      * layout — for example when using a non-standard wide breakout width
 or
      * complex CSS breakpoints.
      *
      * @param string $sizes       The computed `sizes` attribute value.
      * @param string $align       The block alignment ('wide', 'full',
 etc.).
      * @param int    $image_width The intrinsic width of the image in
 pixels.
      */
     return (string) apply_filters( 'block_core_image_sizes', $sizes,
 $align, $image_width );
 }
 }}}

 ----

 === 2. Sizes injection — insert inside `render_block_core_image()`, after
 the `data-id` block and before the `FIGCAPTION` block

 Find this existing comment in `render_block_core_image()`:

 {{{
 #!php
     /*
      * If the `caption` attribute is empty and we encounter a
 `<figcaption>` element,
      * we take note of its span so we can remove it later.
      */
     if ( $processor->next_tag( 'FIGCAPTION' ) && empty(
 $attributes['caption'] ) ) {
 }}}

 Insert the following block '''immediately before''' it:

 {{{
 #!php
     /*
      * Inject a corrected `sizes` attribute for alignwide/alignfull images
 before
      * the processor moves away from the <img> tag.
      *
      * The later wp_filter_content_tags() pass (priority 12 on
 the_content) only
      * computes sizes from the image's registered file size and has no
 knowledge
      * of block alignment, so wide/full aligned images end up with a sizes
 value
      * that is too narrow and causes blurry stretched output. Setting
 sizes here,
      * while we still have the full block attribute context, prevents the
 generic
      * pipeline from overriding it (wp_image_add_srcset_and_sizes()
 preserves any
      * pre-existing sizes attribute).
      *
      * @see https://core.trac.wordpress.org/ticket/45407
      */
     if ( null === $processor->get_attribute( 'sizes' ) ) {
         $image_align    = isset( $attributes['align'] ) ?
 $attributes['align'] : '';
         $img_width      = (int) $processor->get_attribute( 'width' );
         $computed_sizes = block_core_image_calculate_sizes( $image_align,
 $img_width );
         if ( $computed_sizes ) {
             $processor->set_attribute( 'sizes', $computed_sizes );
         }
     }
 }}}

 ----

 === How to reproduce the bug

  1. Upload an image '''≥ 2000px wide''' so WordPress generates `1536w` and
 `2048w` srcset candidates.
  2. Insert an Image block, set display size to '''Large (1024px)''' in the
 block sidebar, set alignment to '''Full Width'''.
  3. Publish and inspect the rendered `<img>` tag.

 '''Without fix:'''
 {{{
 sizes="(max-width: 1024px) 100vw, 1024px"
 }}}
 The `1536w`/`2048w` srcset candidates are never selected. The 1024px file
 is stretched at full viewport width — most visible on Retina/HiDPI
 (simulate via DevTools → DPR: 2).

 '''With fix:'''
 {{{
 sizes="100vw"
 }}}
 On a 1440px Retina viewport the browser correctly downloads the `2048w`
 candidate.

 === Backwards compatibility

  * `wp_calculate_image_sizes()` — signature and filter unchanged
  * `aligncenter` / `alignleft` / `alignright` / no alignment — unaffected,
 existing behaviour preserved
  * Any image where `sizes` is already present in markup — injection is
 skipped entirely
  * Works with block themes (reads `wideSize` from `theme.json`) and
 classic themes (falls back to `$content_width`)
  * New `block_core_image_sizes` filter gives themes/plugins a scoped
 override hook

 Happy to write a formal `.diff` attachment and unit tests once the
 approach is agreed on. Thanks for any feedback!

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/45407#comment:51>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list