Skip to content

Commit

Permalink
Image block: Lightbox animation improvements (WordPress#51721)
Browse files Browse the repository at this point in the history
* Add logic to use low-res image while high-res one is loading

We need to have two <img> elements in the DOM inside the lightbox
because otherwise the image flickers when we change the src.

I removed the src and srcset attributes from the responsive image
when the larger one is loaded to signal that the low-res one is no
longer in use.

* Add logic to preload image on hover

* Update tests

* PHP format

* Prevent responsive images from being loaded unnecessarily

The src attribute on the img elements was causing the srcset
attribute to be added as well before the Interactivity API
could remove them, causing images to be loaded before they
were needed and in the wrong size.

This commit removes the src attribute from the img elements
before they are output to the DOM, and also updates the tests.

* Prevent warning of undefined variable

* Prevent warning of undefined variable - refactored

* Replace getimagesize() with wp_getimagesize()

* Resolve image flash when opening lightbox

Rather than allowing the browser to pick the lightbox's
responsive image, we now explicitly read the currentSrc
attribute from the reference image and also prevent users
from opening the lightbox until the reference image is
fully loaded.

Doing this, we can ensure the zoom animation plays smoothly
and there are no oddities in the UX.

---------

Co-authored-by: Carlos Bravo <carlos.bravo@automattic.com>
  • Loading branch information
2 people authored and sethrubenstein committed Jul 13, 2023
1 parent ae4dd16 commit 7a04f44
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 33 deletions.
61 changes: 53 additions & 8 deletions lib/block-supports/behaviors.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,19 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
// We want to store the src in the context so we can set it dynamically when the lightbox is opened.
$z = new WP_HTML_Tag_Processor( $content );
$z->next_tag( 'img' );

if ( isset( $block['attrs']['id'] ) ) {
$img_src = wp_get_attachment_url( $block['attrs']['id'] );
$img_uploaded_src = wp_get_attachment_url( $block['attrs']['id'] );
$img_metadata = wp_get_attachment_metadata( $block['attrs']['id'] );
$img_width = $img_metadata['width'];
$img_height = $img_metadata['height'];
$img_uploaded_srcset = wp_get_attachment_image_srcset( $block['attrs']['id'] );
} else {
$img_src = $z->get_attribute( 'src' );
$img_uploaded_src = $z->get_attribute( 'src' );
$img_dimensions = wp_getimagesize( $img_uploaded_src );
$img_width = $img_dimensions[0];
$img_height = $img_dimensions[1];
$img_uploaded_srcset = '';
}

$w = new WP_HTML_Tag_Processor( $content );
Expand All @@ -100,24 +109,59 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
$w->set_attribute( 'data-wp-interactive', true );
$w->set_attribute(
'data-wp-context',
sprintf( '{ "core":{ "image": { "initialized": false, "imageSrc": "%s", "lightboxEnabled": false, "lightboxAnimation": "%s", "hideAnimationEnabled": false } } }', $img_src, $lightbox_animation )
sprintf(
'{ "core":
{ "image":
{ "imageLoaded": false,
"initialized": false,
"lightboxEnabled": false,
"hideAnimationEnabled": false,
"preloadInitialized": false,
"lightboxAnimation": "%s",
"imageUploadedSrc": "%s",
"imageCurrentSrc": "",
"imageSrcSet": "%s",
"targetWidth": "%s",
"targetHeight": "%s"
}
}
}',
$lightbox_animation,
$img_uploaded_src,
$img_uploaded_srcset,
$img_width,
$img_height
)
);
$w->next_tag( 'img' );
$w->set_attribute( 'data-wp-effect', 'effects.core.image.setCurrentSrc' );
$body_content = $w->get_updated_html();

// Wrap the image in the body content with a button.
$img = null;
preg_match( '/<img[^>]+>/', $content, $img );
preg_match( '/<img[^>]+>/', $body_content, $img );
$button = '<div class="img-container">
<button type="button" aria-haspopup="dialog" aria-label="' . esc_attr( $aria_label ) . '" data-wp-on--click="actions.core.image.showLightbox"></button>'
<button type="button" aria-haspopup="dialog" aria-label="' . esc_attr( $aria_label ) . '" data-wp-on--click="actions.core.image.showLightbox" data-wp-effect="effects.core.image.preloadLightboxImage"></button>'
. $img[0] .
'</div>';
$body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );

// Add src to the modal image.
$m = new WP_HTML_Tag_Processor( $content );
$m->next_tag( 'figure' );
$m->add_class( 'responsive-image' );
$m->next_tag( 'img' );
$m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.imageSrc' );
$modal_content = $m->get_updated_html();
$m->set_attribute( 'src', '' );
$m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.responsiveImgSrc' );
$initial_image_content = $m->get_updated_html();

$q = new WP_HTML_Tag_Processor( $content );
$q->next_tag( 'figure' );
$q->add_class( 'enlarged-image' );
$q->next_tag( 'img' );
$q->set_attribute( 'src', '' );
$q->set_attribute( 'data-wp-bind--src', 'selectors.core.image.enlargedImgSrc' );
$enlarged_image_content = $q->get_updated_html();

$background_color = esc_attr( wp_get_global_styles( array( 'color', 'background' ) ) );

Expand All @@ -143,7 +187,8 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
<button type="button" aria-label="$close_button_label" style="fill: $close_button_color" class="close-button" data-wp-on--click="actions.core.image.hideLightbox">
$close_button_icon
</button>
$modal_content
$initial_image_content
$enlarged_image_content
<div class="scrim" style="background-color: $background_color"></div>
</div>
HTML;
Expand Down
78 changes: 59 additions & 19 deletions packages/block-library/src/image/interactivity.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,43 @@ store( {
core: {
image: {
showLightbox: ( { context, event } ) => {
// We can't initialize the lightbox until the reference
// image is loaded, otherwise the UX is broken.
if ( ! context.core.image.imageLoaded ) {
return;
}
context.core.image.initialized = true;
context.core.image.lastFocusedElement =
window.document.activeElement;
context.core.image.scrollDelta = 0;

context.core.image.lightboxEnabled = true;
if ( context.core.image.lightboxAnimation === 'zoom' ) {
setZoomStyles(
event.target.nextElementSibling,
context,
event
);
}
// Hide overflow only when the animation is in progress,
// otherwise the removal of the scrollbars will draw attention
// to itself and look like an error
document.documentElement.classList.add(
'has-lightbox-open'
);

// Since the img is hidden and its src not loaded until
// the lightbox is opened, let's create an img element on the fly
// so we can get the dimensions we need to calculate the styles
context.core.image.preloadInitialized = true;
const imgDom = document.createElement( 'img' );

imgDom.onload = function () {
// Enable the lightbox only after the image
// is loaded to prevent flashing of unstyled content
context.core.image.lightboxEnabled = true;
if ( context.core.image.lightboxAnimation === 'zoom' ) {
setZoomStyles( imgDom, context, event );
}

// Hide overflow only when the animation is in progress,
// otherwise the removal of the scrollbars will draw attention
// to itself and look like an error
document.documentElement.classList.add(
'has-lightbox-open'
);
context.core.image.activateLargeImage = true;
};
imgDom.setAttribute( 'src', context.core.image.imageSrc );
imgDom.setAttribute(
'src',
context.core.image.imageUploadedSrc
);
},
hideLightbox: async ( { context, event } ) => {
context.core.image.hideAnimationEnabled = true;
Expand Down Expand Up @@ -131,9 +142,14 @@ store( {
roleAttribute: ( { context } ) => {
return context.core.image.lightboxEnabled ? 'dialog' : '';
},
imageSrc: ( { context } ) => {
responsiveImgSrc: ( { context } ) => {
return context.core.image.activateLargeImage
? ''
: context.core.image.imageCurrentSrc;
},
enlargedImgSrc: ( { context } ) => {
return context.core.image.initialized
? context.core.image.imageSrc
? context.core.image.imageUploadedSrc
: '';
},
},
Expand All @@ -142,6 +158,30 @@ store( {
effects: {
core: {
image: {
setCurrentSrc: ( { context, ref } ) => {
if ( ref.complete ) {
context.core.image.imageLoaded = true;
context.core.image.imageCurrentSrc = ref.currentSrc;
} else {
ref.addEventListener( 'load', function () {
context.core.image.imageLoaded = true;
context.core.image.imageCurrentSrc =
this.currentSrc;
} );
}
},
preloadLightboxImage: ( { context, ref } ) => {
ref.addEventListener( 'mouseover', () => {
if ( ! context.core.image.preloadInitialized ) {
context.core.image.preloadInitialized = true;
const imgDom = document.createElement( 'img' );
imgDom.setAttribute(
'src',
context.core.image.imageUploadedSrc
);
}
} );
},
initLightbox: async ( { context, ref } ) => {
context.core.image.figureRef =
ref.querySelector( 'figure' );
Expand All @@ -163,8 +203,8 @@ store( {
} );

function setZoomStyles( imgDom, context, event ) {
let targetWidth = imgDom.naturalWidth;
let targetHeight = imgDom.naturalHeight;
let targetWidth = context.core.image.targetWidth;
let targetHeight = context.core.image.targetHeight;

const verticalPadding = 40;

Expand Down
25 changes: 19 additions & 6 deletions test/e2e/specs/editor/blocks/image.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,13 +842,19 @@ test.describe( 'Image - interactivity', () => {

const lightbox = page.locator( '.wp-lightbox-overlay' );
await expect( lightbox ).toBeHidden();
const image = lightbox.locator( 'img' );
const responsiveImage = lightbox.locator( '.responsive-image img' );
const enlargedImage = lightbox.locator( '.enlarged-image img' );

await expect( image ).toHaveAttribute( 'src', '' );
await expect( responsiveImage ).toHaveAttribute(
'src',
new RegExp( filename )
);
await expect( enlargedImage ).toHaveAttribute( 'src', '' );

await page.getByRole( 'button', { name: 'Enlarge image' } ).click();

await expect( image ).toHaveAttribute(
await expect( responsiveImage ).toHaveAttribute( 'src', '' );
await expect( enlargedImage ).toHaveAttribute(
'src',
new RegExp( filename )
);
Expand Down Expand Up @@ -1076,12 +1082,19 @@ test.describe( 'Image - interactivity', () => {
await page.goto( `/?p=${ postId }` );

const lightbox = page.locator( '.wp-lightbox-overlay' );
const imageDom = lightbox.locator( 'img' );
await expect( imageDom ).toHaveAttribute( 'src', '' );
const responsiveImage = lightbox.locator( '.responsive-image img' );
const enlargedImage = lightbox.locator( '.enlarged-image img' );

await expect( responsiveImage ).toHaveAttribute(
'src',
new RegExp( imgUrl )
);
await expect( enlargedImage ).toHaveAttribute( 'src', '' );

await page.getByRole( 'button', { name: 'Enlarge image' } ).click();

await expect( imageDom ).toHaveAttribute( 'src', imgUrl );
await expect( responsiveImage ).toHaveAttribute( 'src', '' );
await expect( enlargedImage ).toHaveAttribute( 'src', imgUrl );
} );
} );

Expand Down

0 comments on commit 7a04f44

Please sign in to comment.