Skip to content

Group block: render background image as <img> for srcset/lazy-loading; refactor background inspector controls#75885

Open
JosVelasco wants to merge 8 commits intoWordPress:trunkfrom
JosVelasco:feature/59113-background-image-srcset-lazy-loading
Open

Group block: render background image as <img> for srcset/lazy-loading; refactor background inspector controls#75885
JosVelasco wants to merge 8 commits intoWordPress:trunkfrom
JosVelasco:feature/59113-background-image-srcset-lazy-loading

Conversation

@JosVelasco
Copy link
Contributor

@JosVelasco JosVelasco commented Feb 24, 2026

What does this PR do?

Fixes #59113

1. Background image rendered as <img> on the frontend (PHP)

For Group blocks with an uploaded background image sized Cover or Contain (no tiling repeat), the render_block filter in lib/block-supports/background.php now injects a native <img> element as the first child of the block wrapper instead of using CSS background-image. The wrapper receives position: relative.

'no-repeat' is treated the same as unset — both are compatible with object-fit. Only explicit tiling values (repeat, repeat-x, repeat-y) require the CSS fallback.

This matches the pattern already used by the Cover block and gives browsers access to:

  • srcset / sizes for responsive image selection
  • loading="lazy" (explicit) — deferred loading for below-fold images
  • decoding="async" (explicit) — non-blocking image decode
  • The browser's native image cache (shared with other <img> references to the same attachment)

When conditions are not met (Tile size or tiling repeat), the block falls back to the original CSS background-image path.

The position: relative injection only runs when no position: rule is already present on the wrapper, avoiding stacking context collisions.

2. Editor canvas mirrors the frontend (<img> path)

GroupEdit in packages/block-library/src/group/edit.js applies the same useImgElement logic. When active:

  • The block uses a separate outer wrapper (with position: relative) and an inner wp-block-group__inner-container div — identical to the Cover block pattern — so ButtonBlockAppender and other editor UI are not displaced.
  • CSS background-* properties are stripped from the wrapper style; only position: relative is added.
  • A <img class="wp-block__background-image"> element is rendered as a sibling of the inner container.

3. Fixed background: CSS on the frontend, <img> preview in the editor

background-attachment: fixed is a CSS-only property with no <img> equivalent. The PHP renderer excludes fixed-attachment from the $use_img_element path, so parallax CSS is correctly output on the frontend.

The editor, however, also used to exclude fixed-attachment — which left the block blank, because the editor canvas runs inside an <iframe> where background-attachment: fixed positions the background relative to the iframe's viewport (typically off-screen). The JS useImgElement flag now includes fixed-attachment backgrounds, rendering a static <img> preview instead. This matches the Cover block's established pattern: parallax is a frontend-only effect.

Toggling "Fixed background" off removes the backgroundAttachment attribute entirely rather than writing scroll (the browser default), avoiding unnecessary CSS output. Enabling "Fixed background" also clears the focal point (mirrors toggleParallax in the Cover block — position is viewport-relative under fixed, so the element-relative focal point no longer applies).

4. Inner blocks visible above background <img>

position: absolute on the <img> places it at CSS stacking level 8 (positioned, z-index:auto); normal-flow block-level siblings are at level 3, so they were painted behind the image.

Fixed via a CSS rule in style.scss using the general sibling combinator:

.wp-block-group > .wp-block__background-image ~ * {
    position: relative;
    z-index: 1;
}

This elevates every element that follows the background <img> to stacking level 9 — above the image. The rule is inert when no <img> is present (CSS background path). It mirrors the Cover block's wp-block-cover__inner-container { position: relative; z-index: 1; } pattern.

5. Background inspector controls refactored (Design tab)

packages/block-editor/src/components/global-styles/background-panel.js now renders individual ToolsPanelItem entries for each background setting, matching the Cover block's Settings panel UX:

Panel item Control
Image Existing image-picker dropdown
Focal point FocalPointPicker
Fixed background ToggleControl
Size ToggleGroupControl (Cover / Contain / Tile) + UnitControl when Tile
Repeat ToggleControl (disabled when Cover)

Each item is shown by default and can be individually hidden via the ⋮ menu. ToolsPanel spacing replaces the previous flat layout. The size controls no longer live inside a Dropdown popover — they are directly visible in the inspector.

BackgroundSizeControls (previously a private component in background-image-control/index.js) is removed; its logic now lives in background-panel.js where the ToolsPanel context is available.

Testing instructions

  1. Add a Group block to a page/post.
  2. In the Design tab → Background panel, upload or select a media-library image.
  3. Cover (default):
    • Editor canvas: block wrapper has position: relative; a <img class="wp-block__background-image"> is rendered as the background; block contents sit on top and are fully readable.
    • Frontend: same <img> with srcset, loading="lazy", decoding="async".
  4. Contain: same <img> behaviour (no-repeat is set internally — still uses <img>).
  5. Tile: CSS background-image used (no <img>); Repeat toggle is enabled.
  6. Repeat toggled on: CSS fallback; <img> not used.
  7. Fixed background toggled on:
    • Frontend: CSS fallback; background-attachment: fixed applies correctly (parallax).
    • Editor canvas: <img> is shown as a static preview (CSS fixed is off-screen in an iframe).
    • Toggle it off — no background-attachment attribute is saved.
  8. Inner blocks are visible: add a Paragraph (or any block) inside the Group — it should appear above the background image, not hidden behind it.
  9. Individual panel items (Focal point, Fixed background, Size, Repeat) can be hidden/shown via the ⋮ menu in the Background panel.
  10. Existing Group blocks without a background image are unaffected.

PHPUnit tests

vendor/bin/phpunit --filter WP_Block_Supports_Background_Test

Covers:

  • <img> injected for cover/contain uploaded image (null repeat, no fixed attachment)
  • <img> injected for contain uploaded image with no-repeat explicitly set
  • CSS fallback for tile size
  • CSS fallback for tiled repeat
  • CSS fallback for fixed attachment
  • No-op when block does not support background image

Before:

background.group.before.mp4

After:

background.srcset.group.after.mp4

AI assistance disclosure

  • AI assistance: Yes
  • Tool: Claude (Anthropic)
  • What AI generated: Implementation code and tests across all modified files, iterative debugging of editor-canvas rendering (Cover block pattern adoption, ButtonBlockAppender positioning, z-index stacking fix), lint error resolution, and applying code review feedback.
  • Human review: All generated code was reviewed, tested in the browser (editor canvas + frontend), and adjusted based on observed behaviour and review feedback before committing.

…, improve background controls UX

For uploaded images with cover/contain sizing and no repeat (and no fixed
attachment), the PHP `render_block` filter now injects a native `<img>` element
(with srcset, sizes, loading="lazy", decoding="async") as the first child of the
block wrapper, and adds `position:relative` to the wrapper. This matches the Cover
block pattern and improves performance for media-library background images.

The editor canvas mirrors this via `useImgElement` logic in `GroupEdit`: when the
same conditions hold, the wrapper uses a separate outer element (with
`position:relative`) and an inner `wp-block-group__inner-container` div, with the
`<img>` as a sibling, identical to how the Cover block avoids displacing the
ButtonBlockAppender.

`background-attachment:fixed` correctly falls back to CSS background-image in both
PHP and JS, since `background-attachment` is a CSS-only property that does not
apply to `<img>` elements.

Background inspector controls are refactored to use individual `ToolsPanelItem`
entries (Focal point, Fixed background, Size, Repeat) matching the Cover block
Settings panel UX, so each control is individually shown/hidden via the panel
menu with proper spacing. The size controls no longer live inside a Dropdown
popover.

Includes PHPUnit tests for the `<img>`-path and CSS-fallback cases.
@github-actions github-actions bot added [Package] Block library /packages/block-library [Package] Block editor /packages/block-editor labels Feb 24, 2026
- Allow 'no-repeat' backgroundRepeat in the <img> path (PHP and JS).
  `contain` size always sets backgroundRepeat:'no-repeat' via the UI, so
  the previous `empty()` guard was blocking the optimization for the most
  common contain use-case.
- Remove `alignfull` from the background <img> class in both PHP and JS;
  it can trigger theme layout styles not intended for a positioned,
  pointer-events-none layer.
- Add explicit loading="lazy" and decoding="async" attrs to the PHP
  wp_get_attachment_image() call so the optimization is guaranteed.
- Guard the position:relative injection with a regex check to avoid
  clobbering or duplicating an existing position rule on the wrapper.
- Set backgroundAttachment to undefined (not 'scroll') when toggling off
  fixed background, avoiding unnecessary CSS output of the default value.
- Add PHPUnit test covering the no-repeat + contain -> <img> path.
Mirrors the Cover block's toggleParallax behavior (PR WordPress#74600): when
enabling background-attachment:fixed the element-relative focal point
no longer makes sense (the browser positions the background relative to
the viewport, not the element), so backgroundPosition is cleared to give
the user a clean starting point.

The focal point picker remains visible — the position value it produces
is still meaningful as a fallback on mobile devices where fixed attachment
is not supported and the background reverts to scroll.

Fixes the same class of bug that PR WordPress#74600 addressed for the Cover block.
…ackgrounds

When background-attachment:fixed is set, the editor canvas (an iframe) positions
CSS background-image relative to the iframe viewport — the background is typically
off-screen and invisible to the author. The PHP renderer correctly applies
background-attachment:fixed on the frontend via CSS.

Aligns with the Cover block pattern: the editor always uses an <img> element for
media-library cover/contain images, giving a reliable static preview regardless of
attachment mode. The parallax effect is visible on the frontend as expected.
position:absolute on the background <img> places it at CSS stacking level 8
(positioned, z-index:auto); normal-flow block-level siblings paint at level 3,
so they were obscured by the image.

Fix via the ~ (general sibling combinator): every element that follows
.wp-block__background-image inside a .wp-block-group gets position:relative
and z-index:1, elevating it to stacking level 9 — above the image.

This mirrors the Cover block pattern where .wp-block-cover__inner-container
has position:relative; z-index:1 in CSS. The rule is self-contained: it only
activates when the background <img> is present as a direct child, and does
not affect the layout CSS classes on the outer wrapper.
…round

Adds a data provider case verifying that when backgroundAttachment is 'fixed'
the $use_img_element path is skipped (empty('fixed') is false) and the CSS
background-image fallback is used instead.
@JosVelasco
Copy link
Contributor Author

The CI check for Verify Core Backport Changelog is failing because a Core PR on wordpress-develop has not been created yet for the PHP changes in lib/block-supports/background.php and phpunit/block-supports/background-test.php.

A Core backport PR will be created and a corresponding backport-changelog/7.1/<core-pr-number>.md entry will be added before merge.

@JosVelasco JosVelasco marked this pull request as ready for review February 25, 2026 18:16
@github-actions
Copy link

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: [Package] Block library, [Package] Block editor.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions
Copy link

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: JosVelasco <josvelasco@git.wordpress.org>
Co-authored-by: ramonjd <ramonopoly@git.wordpress.org>
Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: andrewserong <andrewserong@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@JosVelasco
Copy link
Contributor Author

Core backport PR created: WordPress/wordpress-develop#11050

The entry has been added to this branch. The Verify Core Backport Changelog CI check should now pass.

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

Labels

[Package] Block editor /packages/block-editor [Package] Block library /packages/block-library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Background image block support: optimize image output

1 participant