Group block: render background image as <img> for srcset/lazy-loading; refactor background inspector controls#75885
Conversation
…, 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.
- 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.
|
The CI check for Verify Core Backport Changelog is failing because a Core PR on A Core backport PR will be created and a corresponding |
|
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.
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. |
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
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. |
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_blockfilter inlib/block-supports/background.phpnow injects a native<img>element as the first child of the block wrapper instead of using CSSbackground-image. The wrapper receivesposition: relative.'no-repeat'is treated the same as unset — both are compatible withobject-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/sizesfor responsive image selectionloading="lazy"(explicit) — deferred loading for below-fold imagesdecoding="async"(explicit) — non-blocking image decode<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-imagepath.The
position: relativeinjection only runs when noposition:rule is already present on the wrapper, avoiding stacking context collisions.2. Editor canvas mirrors the frontend (
<img>path)GroupEditinpackages/block-library/src/group/edit.jsapplies the sameuseImgElementlogic. When active:position: relative) and an innerwp-block-group__inner-containerdiv — identical to the Cover block pattern — soButtonBlockAppenderand other editor UI are not displaced.background-*properties are stripped from the wrapper style; onlyposition: relativeis added.<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 editorbackground-attachment: fixedis a CSS-only property with no<img>equivalent. The PHP renderer excludes fixed-attachment from the$use_img_elementpath, 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>wherebackground-attachment: fixedpositions the background relative to the iframe's viewport (typically off-screen). The JSuseImgElementflag 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
backgroundAttachmentattribute entirely rather than writingscroll(the browser default), avoiding unnecessary CSS output. Enabling "Fixed background" also clears the focal point (mirrorstoggleParallaxin the Cover block — position is viewport-relative underfixed, so the element-relative focal point no longer applies).4. Inner blocks visible above background
<img>position: absoluteon 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.scssusing the general sibling combinator: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'swp-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.jsnow renders individualToolsPanelItementries for each background setting, matching the Cover block's Settings panel UX:FocalPointPickerToggleControlToggleGroupControl(Cover / Contain / Tile) +UnitControlwhen TileToggleControl(disabled when Cover)Each item is shown by default and can be individually hidden via the ⋮ menu.
ToolsPanelspacing replaces the previous flat layout. The size controls no longer live inside aDropdownpopover — they are directly visible in the inspector.BackgroundSizeControls(previously a private component inbackground-image-control/index.js) is removed; its logic now lives inbackground-panel.jswhere theToolsPanelcontext is available.Testing instructions
position: relative; a<img class="wp-block__background-image">is rendered as the background; block contents sit on top and are fully readable.<img>withsrcset,loading="lazy",decoding="async".<img>behaviour (no-repeatis set internally — still uses<img>).background-imageused (no<img>); Repeat toggle is enabled.<img>not used.background-attachment: fixedapplies correctly (parallax).<img>is shown as a static preview (CSSfixedis off-screen in an iframe).background-attachmentattribute is saved.PHPUnit tests
Covers:
<img>injected for cover/contain uploaded image (null repeat, no fixed attachment)<img>injected for contain uploaded image withno-repeatexplicitly setBefore:
background.group.before.mp4
After:
background.srcset.group.after.mp4
AI assistance disclosure
ButtonBlockAppenderpositioning, z-index stacking fix), lint error resolution, and applying code review feedback.