Adding Image Controls to a Gutenberg Block
A custom block I worked on recently needed the ability to display an image. To do that, I needed a control to add or upload an image from the Media Library.
Add the attributes
The block needed attributes to store the image ID, URL, and alt text. The alt
text is read from the alt
attribute on the block’s img
tag.
"imageUrl": {
"type": "string"
},
"imageId": {
"type": "number"
},
"imageAlt": {
"type": "string",
"source": "attribute",
"selector": "img",
"attribute": "alt",
"default": ""
}
Handle Image Attribute Changes
We’ll define a function to update the attributes whenever a new media object was passed in. The media object includes the image ID, URL, and alt text.
const setImageAttributes = (media) => {
if (!media || !media.url) {
setAttributes({
imageUrl: null,
imageId: null,
imageAlt: null,
});
return;
}
setAttributes({
imageUrl: media.url,
imageId: media.id,
imageAlt: media?.alt,
});
};
Add the MediaPlaceholder Component
The MediaPlaceholder component renders the editing interface to replace the media for a block. Here’s it’s configured to allow only images (but images of any filetype) and to allow only one image to be selected.
<MediaPlaceholder
accept="image/*"
allowedTypes={['image']}
onSelect={setImageAttributes}
multiple={false}
handleUpload={true}
/>
Add the MediaReplaceFlow component
The MediaReplaceFlow component, added to the “Block Tools”, gives the user the option to click “Add Image”/“Replace Image” in the block toolbar in addition to using the MediaPlaceholder interface.
<BlockControls>
<MediaReplaceFlow
mediaId={imageId}
mediaUrl={imageUrl}
allowedTypes={['image']}
accept="image/*"
onSelect={setImageAttributes}
name={!imageUrl ? __('Add Image') : __('Replace Image')}
/>
</BlockControls>
Display the image
Add an img
tag if the URL exists
Once the image has been uploaded and the imageUrl
attribute property is set,
the image can be displayed on the block using an img
tag.
{imageUrl && <img src={imageUrl} alt={imageAlt} />}
Position the MediaPlaceholder
The MediaPlaceholder interface is a regular div
and will display below the
img (or wherever you put it in the block). To position it on top of the image,
similar to the Cover block, we need to move it with a bit of (S)CSS. (.card__media
here is a custom class that wraps around both the img
and the MediaPlaceholder
.)
.card__media {
min-height: 200px;
position: relative;
}
.card__media img + .block-editor-media-placeholder {
background-color: rgba(255, 255, 255, 0.7);
box-shadow: none;
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
Add a Spinner
One last touch is to display the <Spinner />
component while the image is
being uploaded. While that’s happening, the imageUrl will be set to a temporary
Blob URL and the ID won’t exist.
import { isBlobURL } from '@wordpress/blob';
const isTemporaryMedia = (id, url) => !id && isBlobURL(url);
Then within the component, we can check if the image is uploading by checking if it’s temporary, and displaying the spinner if so.
const isUploadingMedia = isTemporaryMedia(imageId, imageUrl);
...
{isUploadingMedia && <Spinner />}
References
- https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/cover
- https://github.com/WordPress/gutenberg/blob/trunk/packages/block-editor/src/components/media-placeholder/README.md
- https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/media-replace-flow/README.md