diff --git a/entry_types/scrolled/config/locales/de.yml b/entry_types/scrolled/config/locales/de.yml
index 47197a4af5..6b2e784a28 100644
--- a/entry_types/scrolled/config/locales/de.yml
+++ b/entry_types/scrolled/config/locales/de.yml
@@ -2,6 +2,8 @@ de:
pageflow:
datawrapper_chart_embed_opt_in:
feature_name: Opt-in für Datawrapper Embeds
+ image_srcset:
+ feature_name: Responsive Bild-Srcset
defaultNavigation:
widget_type_name: Standard Navigation
editor:
diff --git a/entry_types/scrolled/config/locales/en.yml b/entry_types/scrolled/config/locales/en.yml
index c5b14f9b6d..2473ea8d90 100644
--- a/entry_types/scrolled/config/locales/en.yml
+++ b/entry_types/scrolled/config/locales/en.yml
@@ -2,6 +2,8 @@ en:
pageflow:
datawrapper_chart_embed_opt_in:
feature_name: Opt-in for Datawrapper embeds
+ image_srcset:
+ feature_name: Responsive image srcset
defaultNavigation:
widget_type_name: Default navigation
editor:
diff --git a/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb b/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb
index 5f10ce05f0..d7722cdc2c 100644
--- a/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb
+++ b/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb
@@ -196,6 +196,8 @@ def configure(config)
c.features.register('custom_palette_colors')
c.features.register('decoration_effects')
c.features.register('backdrop_size')
+ c.features.register('image_srcset')
+ c.features.enable_by_default('image_srcset')
c.features.register('faq_page_structured_data') do |feature_config|
feature_config.entry_structured_data_types.register(
diff --git a/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js b/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js
index e8a8379445..2c563fae9c 100644
--- a/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js
+++ b/entry_types/scrolled/package/spec/contentElements/inlineImage/InlineImage-spec.js
@@ -1,7 +1,11 @@
+import React from 'react';
import 'contentElements/inlineImage/frontend';
import {renderContentElement, usePageObjects} from 'support/pageObjects';
+import {renderInContentElement} from 'pageflow-scrolled/testHelpers';
import '@testing-library/jest-dom/extend-expect'
+import {InlineImage} from 'contentElements/inlineImage/InlineImage';
+import {features} from 'pageflow/frontend';
import {usePortraitOrientation} from 'frontend/usePortraitOrientation';
jest.mock('frontend/usePortraitOrientation');
@@ -190,6 +194,118 @@ describe('InlineImage', () => {
});
});
+ describe('srcset', () => {
+ beforeEach(() => features.enable('frontend', ['image_srcset']));
+ afterEach(() => features.enabledFeatureNames = []);
+
+ function renderInlineImage({contentElementWidth = 0, ...seedOptions} = {}) {
+ const result = renderInContentElement(
+ ,
+ {seed: seedOptions}
+ );
+ result.simulateScrollPosition('near viewport');
+ return result;
+ }
+
+ it('uses medium and large srcset for default width', () => {
+ const {getByRole} = renderInlineImage({
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [{permaId: 100, id: 1, width: 4000, height: 3000}]
+ });
+
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/medium/image.jpg 1024w, 000/000/001/large/image.jpg 1920w');
+ expect(getByRole('img')).toHaveAttribute('sizes',
+ '(min-width: 950px) 950px, 100vw');
+ });
+
+ it('uses medium, large and ultra srcset for full width', () => {
+ const {getByRole} = renderInlineImage({
+ contentElementWidth: 3,
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg',
+ ultra: ':id_partition/ultra/image.jpg'
+ },
+ imageFiles: [{permaId: 100, id: 1, width: 4000, height: 3000}]
+ });
+
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/medium/image.jpg 1024w, ' +
+ '000/000/001/large/image.jpg 1920w, ' +
+ '000/000/001/ultra/image.jpg 3840w');
+ expect(getByRole('img')).toHaveAttribute('sizes', '100vw');
+ });
+
+ it('uses medium, large and ultra srcset with 1200px sizes for xl width', () => {
+ const {getByRole} = renderInlineImage({
+ contentElementWidth: 2,
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg',
+ ultra: ':id_partition/ultra/image.jpg'
+ },
+ imageFiles: [{permaId: 100, id: 1, width: 4000, height: 3000}]
+ });
+
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/medium/image.jpg 1024w, ' +
+ '000/000/001/large/image.jpg 1920w, ' +
+ '000/000/001/ultra/image.jpg 3840w');
+ expect(getByRole('img')).toHaveAttribute('sizes',
+ '(min-width: 950px) 1200px, 100vw');
+ });
+
+ it('uses computed width descriptors for portrait images', () => {
+ const {getByRole} = renderInlineImage({
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [{permaId: 100, id: 1, width: 2160, height: 3840}]
+ });
+
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/medium/image.jpg 576w, 000/000/001/large/image.jpg 1080w');
+ });
+
+ it('uses plain medium variant for small widths', () => {
+ const {getByRole} = renderInlineImage({
+ contentElementWidth: -1,
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [{permaId: 100, id: 1, width: 200, height: 100}]
+ });
+
+ expect(getByRole('img')).not.toHaveAttribute('srcset');
+ expect(getByRole('img')).toHaveAttribute('src',
+ '000/000/001/medium/image.jpg');
+ });
+
+ it('falls back to original behavior when feature is disabled', () => {
+ features.enabledFeatureNames = [];
+
+ const {getByRole} = renderInlineImage({
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [{permaId: 100, id: 1, width: 200, height: 100}]
+ });
+
+ expect(getByRole('img')).not.toHaveAttribute('srcset');
+ expect(getByRole('img')).toHaveAttribute('src',
+ '000/000/001/medium/image.jpg');
+ });
+ });
+
describe('basic functionality', () => {
it('renders with FitViewport and ContentElementBox', () => {
const {getContentElement} = renderContentElement({
diff --git a/entry_types/scrolled/package/spec/entryState/useFile-spec.js b/entry_types/scrolled/package/spec/entryState/useFile-spec.js
index 43350019cf..a750927652 100644
--- a/entry_types/scrolled/package/spec/entryState/useFile-spec.js
+++ b/entry_types/scrolled/package/spec/entryState/useFile-spec.js
@@ -211,6 +211,140 @@ describe('useFile', () => {
});
});
+ it('includes variantWidths for image files', () => {
+ const {result} = renderHookInEntry(
+ () => useFile({collectionName: 'imageFiles', permaId: 1}),
+ {
+ seed: {
+ fileUrlTemplates: {
+ imageFiles: {
+ medium: '/image_files/:id_partition/medium/:basename.:processed_extension',
+ large: '/image_files/:id_partition/large/:basename.:processed_extension',
+ }
+ },
+ fileModelTypes: {
+ imageFiles: 'Pageflow::ImageFile'
+ },
+ imageFiles: [
+ {
+ id: 100,
+ permaId: 1,
+ basename: 'image',
+ extension: 'jpg',
+ processedExtension: 'webp',
+ width: 4000,
+ height: 3000
+ }
+ ]
+ }
+ }
+ );
+
+ expect(result.current.variantWidths).toEqual([
+ ['1024w', 'medium'],
+ ['1920w', 'large']
+ ]);
+ });
+
+ it('computes variantWidths based on actual image dimensions for portrait images', () => {
+ const {result} = renderHookInEntry(
+ () => useFile({collectionName: 'imageFiles', permaId: 1}),
+ {
+ seed: {
+ fileUrlTemplates: {
+ imageFiles: {
+ medium: '/image_files/:id_partition/medium/:basename.:processed_extension',
+ large: '/image_files/:id_partition/large/:basename.:processed_extension',
+ }
+ },
+ fileModelTypes: {
+ imageFiles: 'Pageflow::ImageFile'
+ },
+ imageFiles: [
+ {
+ id: 100,
+ permaId: 1,
+ basename: 'image',
+ extension: 'jpg',
+ processedExtension: 'webp',
+ width: 2160,
+ height: 3840
+ }
+ ]
+ }
+ }
+ );
+
+ expect(result.current.variantWidths).toEqual([
+ ['576w', 'medium'],
+ ['1080w', 'large']
+ ]);
+ });
+
+ it('deduplicates variantWidths when variants produce same width', () => {
+ const {result} = renderHookInEntry(
+ () => useFile({collectionName: 'imageFiles', permaId: 1}),
+ {
+ seed: {
+ fileUrlTemplates: {
+ imageFiles: {
+ medium: '/image_files/:id_partition/medium/:basename.:processed_extension',
+ large: '/image_files/:id_partition/large/:basename.:processed_extension',
+ ultra: '/image_files/:id_partition/ultra/:basename.:processed_extension',
+ }
+ },
+ fileModelTypes: {
+ imageFiles: 'Pageflow::ImageFile'
+ },
+ imageFiles: [
+ {
+ id: 100,
+ permaId: 1,
+ basename: 'image',
+ extension: 'jpg',
+ processedExtension: 'webp',
+ width: 1920,
+ height: 1080
+ }
+ ]
+ }
+ }
+ );
+
+ expect(result.current.variantWidths).toEqual([
+ ['1024w', 'medium'],
+ ['1920w', 'large']
+ ]);
+ });
+
+ it('does not include variantWidths for video files', () => {
+ const {result} = renderHookInEntry(
+ () => useFile({collectionName: 'videoFiles', permaId: 1}),
+ {
+ seed: {
+ fileUrlTemplates: {
+ videoFiles: {
+ high: '/video_files/:id_partition/high.mp4',
+ },
+ },
+ fileModelTypes: {
+ videoFiles: 'Pageflow::VideoFile'
+ },
+ videoFiles: [
+ {
+ id: 100,
+ permaId: 1,
+ basename: 'video',
+ variants: ['high'],
+ }
+ ]
+ }
+ }
+ );
+
+ expect(result.current.variantWidths).toBeUndefined();
+ });
+
it('falls back to file name for display name from watched collection', () => {
const {result} = renderHookInEntry(
() => useFile({collectionName: 'imageFiles', permaId: 1}),
diff --git a/entry_types/scrolled/package/spec/frontend/Image-spec.js b/entry_types/scrolled/package/spec/frontend/Image-spec.js
index 5429a40d93..f92aa17d2e 100644
--- a/entry_types/scrolled/package/spec/frontend/Image-spec.js
+++ b/entry_types/scrolled/package/spec/frontend/Image-spec.js
@@ -359,6 +359,175 @@ describe('Image', () => {
expect(getByRole('img').hasAttribute('alt')).toBe(true);
});
+ it('renders srcset with width descriptors for array variant', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ large: ':id_partition/large/image.jpg',
+ ultra: ':id_partition/ultra/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100, width: 4000, height: 3000}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('src', '000/000/001/large/image.jpg');
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/large/image.jpg 1920w, 000/000/001/ultra/image.jpg 3840w');
+ expect(getByRole('img')).toHaveAttribute('sizes', '100vw');
+ });
+
+ it('passes custom sizes prop through', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ large: ':id_partition/large/image.jpg',
+ ultra: ':id_partition/ultra/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100, width: 4000, height: 3000}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('sizes', '(min-width: 960px) 50vw, 100vw');
+ });
+
+ it('treats single-element array variant like string variant', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('src', '000/000/001/large/image.jpg');
+ expect(getByRole('img')).not.toHaveAttribute('srcset');
+ });
+
+ it('skips srcset for SVG when preferSvg is true', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ original: ':id_partition/original/:basename.:extension',
+ large: ':id_partition/large/image.jpg',
+ ultra: ':id_partition/ultra/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100, basename: 'image', extension: 'svg'}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('src', '000/000/001/original/image.svg');
+ expect(getByRole('img')).not.toHaveAttribute('srcset');
+ });
+
+ it('does not render srcset for string variant', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).not.toHaveAttribute('srcset');
+ });
+
+ it('skips srcset when variants compute to same width', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ large: ':id_partition/large/image.jpg',
+ ultra: ':id_partition/ultra/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100, width: 1920, height: 1080}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('src', '000/000/001/large/image.jpg');
+ expect(getByRole('img')).not.toHaveAttribute('srcset');
+ });
+
+ it('uses computed width descriptors for portrait images', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100, width: 2160, height: 3840}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/medium/image.jpg 576w, 000/000/001/large/image.jpg 1080w');
+ });
+
+ it('uses correct width descriptors for medium and large array variant', () => {
+ const {getByRole} = renderInEntry(
+ () => ,
+ {
+ seed: {
+ imageFileUrlTemplates: {
+ medium: ':id_partition/medium/image.jpg',
+ large: ':id_partition/large/image.jpg'
+ },
+ imageFiles: [
+ {id: 1, permaId: 100}
+ ]
+ }
+ }
+ );
+
+ expect(getByRole('img')).toHaveAttribute('srcset',
+ '000/000/001/medium/image.jpg 1024w, 000/000/001/large/image.jpg 1920w');
+ });
+
it('supports width and height attributes', () => {
const {getByRole} = renderInEntry(
() =>
@@ -120,6 +120,31 @@ function ImageWithCaption({
);
}
+function imageVariantAndSizes(contentElementWidth) {
+ if (!features.isEnabled('image_srcset')) {
+ return {
+ variant: contentElementWidth === contentElementWidths.full ? 'large' : 'medium'
+ };
+ }
+
+ if (contentElementWidth >= contentElementWidths.xl) {
+ return {
+ variant: ['medium', 'large', 'ultra'],
+ sizes: contentElementWidth === contentElementWidths.full ?
+ '100vw' : '(min-width: 950px) 1200px, 100vw'
+ };
+ }
+
+ if (contentElementWidth >= contentElementWidths.md) {
+ return {
+ variant: ['medium', 'large'],
+ sizes: '(min-width: 950px) 950px, 100vw'
+ };
+ }
+
+ return {variant: 'medium'};
+}
+
function processImageModifiers(imageModifiers) {
const cropValue = getModiferValue(imageModifiers, 'crop');
const isCircleCrop = cropValue === 'circle';
diff --git a/entry_types/scrolled/package/src/entryState/extendFile.js b/entry_types/scrolled/package/src/entryState/extendFile.js
index c439bd4499..52427ea879 100644
--- a/entry_types/scrolled/package/src/entryState/extendFile.js
+++ b/entry_types/scrolled/package/src/entryState/extendFile.js
@@ -1,44 +1,34 @@
export function extendFile(collectionName, file, config) {
- return addModelType(
- collectionName,
- expandUrls(
- collectionName,
- file,
- config.fileUrlTemplates
- ),
- config.fileModelTypes
- );
-}
-
-function addModelType(collectionName, file, modelTypes) {
if (!file) {
return null;
}
- if (!modelTypes[collectionName]) {
- throw new Error(`Could not find model type for collection name ${collectionName}`);
- }
+ const variants = file.variants ?
+ ['original', ...file.variants] :
+ Object.keys(config.fileUrlTemplates[collectionName] || {});
return {
...file,
- modelType: modelTypes[collectionName]
+ modelType: resolveModelType(collectionName, config.fileModelTypes),
+ urls: buildUrls(collectionName, file, variants, config.fileUrlTemplates),
+ variantWidths: computeVariantWidths(collectionName, file, variants)
};
}
-function expandUrls(collectionName, file, urlTemplates) {
- if (!file) {
- return null;
+function resolveModelType(collectionName, modelTypes) {
+ if (!modelTypes[collectionName]) {
+ throw new Error(`Could not find model type for collection name ${collectionName}`);
}
+ return modelTypes[collectionName];
+}
+
+function buildUrls(collectionName, file, variants, urlTemplates) {
if (!urlTemplates[collectionName]) {
throw new Error(`No file url templates found for ${collectionName}`);
}
- const variants = file.variants ?
- ['original', ...file.variants] :
- Object.keys(urlTemplates[collectionName]);
-
- const urls = variants.reduce((result, variant) => {
+ return variants.reduce((result, variant) => {
const url = getFileUrl(collectionName,
file,
variant,
@@ -50,11 +40,6 @@ function expandUrls(collectionName, file, urlTemplates) {
return result;
}, {});
-
- return {
- urls,
- ...file
- };
}
function getFileUrl(collectionName, file, quality, urlTemplates) {
@@ -91,3 +76,42 @@ function hlsQualities(file) {
.filter(quality => file.variants.includes(quality))
.join(',');
}
+
+const variantGeometries = {
+ imageFiles: {medium: 1024, large: 1920, ultra: 3840}
+};
+
+function computeVariantWidths(collectionName, file, variants) {
+ const geometries = variantGeometries[collectionName];
+
+ if (!geometries) {
+ return undefined;
+ }
+
+ const widthToVariant = {};
+
+ variants.forEach(variant => {
+ const geometrySize = geometries[variant];
+
+ if (geometrySize) {
+ const key = variantWidth(file, geometrySize) + 'w';
+
+ if (!widthToVariant[key]) {
+ widthToVariant[key] = variant;
+ }
+ }
+ });
+
+ return Object.entries(widthToVariant);
+}
+
+function variantWidth(file, geometrySize) {
+ const {width, height} = file;
+
+ if (!width || !height) {
+ return geometrySize;
+ }
+
+ const scale = Math.min(geometrySize / width, geometrySize / height, 1);
+ return Math.round(width * scale);
+}
diff --git a/entry_types/scrolled/package/src/entryState/useFile.js b/entry_types/scrolled/package/src/entryState/useFile.js
index 17c29d8c55..5423b1130f 100644
--- a/entry_types/scrolled/package/src/entryState/useFile.js
+++ b/entry_types/scrolled/package/src/entryState/useFile.js
@@ -1,3 +1,5 @@
+import {useMemo} from 'react';
+
import {useEntryStateCollectionItem, useEntryStateConfig} from './EntryStateProvider';
import {extendFile} from './extendFile';
@@ -26,10 +28,10 @@ import {extendFile} from './extendFile';
*/
export function useFile({collectionName, permaId}) {
const file = useEntryStateCollectionItem(collectionName, permaId);
+ const config = useEntryStateConfig();
- return extendFile(
- collectionName,
- file,
- useEntryStateConfig()
+ return useMemo(
+ () => extendFile(collectionName, file, config),
+ [collectionName, file, config]
);
}
diff --git a/entry_types/scrolled/package/src/frontend/Image.js b/entry_types/scrolled/package/src/frontend/Image.js
index 5c4a6eeb89..e78526d6b6 100644
--- a/entry_types/scrolled/package/src/frontend/Image.js
+++ b/entry_types/scrolled/package/src/frontend/Image.js
@@ -9,7 +9,10 @@ import {ImageStructuredData} from './ImageStructuredData';
*
* @param {Object} props
* @param {Object} props.imageFile - Image file obtained via `useFile`.
- * @param {string} [props.variant] - Paperclip style to use. Defaults to large.
+ * @param {string|string[]} [props.variant] - Paperclip style to use. Defaults to
+ * large. Pass an array (e.g. `['large', 'ultra']`) to emit a `srcset` with
+ * width descriptors. The first entry is used as `src` fallback.
+ * @param {string} [props.sizes] - Sizes attribute for srcset. Defaults to `"100vw"`.
* @param {boolean} [props.load] - Whether to load the image. Can be used for lazy loading.
* @param {boolean} [props.structuredData] - Whether to render a JSON+LD script tag.
* @param {boolean} [props.preferSvg] - Use original if image is SVG.
@@ -36,7 +39,9 @@ function renderImageTag(props, imageFile) {
return (
variant.includes(v) && imageFile.urls[v])
+ .map(([w, v]) => `${imageFile.urls[v]} ${w}`);
+
+ if (entries.length <= 1) return undefined;
+
+ return entries.join(', ');
+}
+
+function imageSizes(imageFile, props) {
+ if (imageSrcSet(imageFile, props)) {
+ return props.sizes || '100vw';
+ }
+
+ return undefined;
+}
+
function imageUrl(imageFile, {variant, preferSvg}) {
if (variant === 'ultra' && !imageFile.urls.ultra) {
variant = 'large';
diff --git a/entry_types/scrolled/package/src/frontend/v1/Backdrop/BackgroundImage.js b/entry_types/scrolled/package/src/frontend/v1/Backdrop/BackgroundImage.js
index 230dfa563b..3ae1baa781 100644
--- a/entry_types/scrolled/package/src/frontend/v1/Backdrop/BackgroundImage.js
+++ b/entry_types/scrolled/package/src/frontend/v1/Backdrop/BackgroundImage.js
@@ -4,6 +4,7 @@ import {Image} from '../../Image';
import {MotifArea} from '../MotifArea';
import {useSectionLifecycle} from '../../useSectionLifecycle';
import {Effects} from './Effects';
+import {features} from 'pageflow/frontend';
export function BackgroundImage({image, onMotifAreaUpdate, containerDimension}) {
const {shouldLoad} = useSectionLifecycle();
@@ -14,7 +15,9 @@ export function BackgroundImage({image, onMotifAreaUpdate, containerDimension})
+ preferSvg={true}
+ variant={features.isEnabled('image_srcset') ?
+ ['medium', 'large', 'ultra'] : 'large'} />