Skip to content

Commit

Permalink
Image block: Add aspect ratio support to lightbox (#52765)
Browse files Browse the repository at this point in the history
* Add docs for `afterLoad`

* Move store options to the end

* Initial aspect-ration support implementation

* Move `inherit` property to `wp-style` directive

* Add `object-fit` cover

* Fix big images aspect ratio

* Fix animation on mobile; add comments

* Remove obsolete variable declaration

* Fix PHP spacing

* Optimize animation performance; add comments; fix fade and thumbnail bugs

* Handle images in content with aspect ratios that differ from source images

Note: This commit does not yet contain support for images with the
`contain` style enabled, or for the images with 16x9 aspect ratio.

For the moment, This code works by checking the aspect ratio of
the image's natural dimensions and comparing it to the target dimensions.

If these aspect ratios differ, then we calculate the space the lightbox
image would use as a cover image and apply that as a `scale` CSS attribute.

It also currently sets the image as a square using that resized dimension,
which seems to work in many but not all cases, so will continue to explore
the best way to approach.

Animation for translation and scale have been separated in this commit
to allow for flexibility in handling various scenarios.

* Simplify zooming lightbox

* Add support for thumbnails

* Clean code and change variable names

* Fix thumbnails with cropped width and height

* Add support for contain setting

* Add 5% padding to the lightbox container

* Move CSS properties to style tag

* Add wp prefix to CSS global variables

* Remove `!important` from CSS

* Reuse zooming logic for the fade animation

* Remove fade conditional

* Fix php standards

* Remove unnecessary inheritSize selector

* Update e2e test to fit new style tag

* Fix width of image container so button isn't wider than image

* Add variable horizontal padding and fixed vertical padding

* Fix fade out animation; modify animation for image slightly

* Prevent scroll after focusing last image

* Add support for contain in lightbox image button

* Add logic to set button styles only when needed and on image load

* Simplify lazy loading logic

* Remove extra div from image markup

As part of this commit, I added handling for button styles
in all images with the lightbox enabled, as we can't rely
on the CSS as was written to set the right dimensions
without breaking the layout on mobile.

* Add 1 pixel to container dimensions to fix style bug on iOS

* Add logic to center button when using 'contain'

* (After merge) Add logic to set button styles on window resize

* Replace loading=lazy with manual setting of src attribute

The combination of 'loading=lazy' and the 'hidden' attributes
was breaking progressive image loading in Safari and Chrome; instead
we are now taking what seems to be a more reliable approach
to accomplish lazy loading of the enlarged image, namely
setting the src manually when the lightbox is opened.

*This was an approach we had implemented before, but we began using
'loading=lazy' during a code cleanup.

* Add link to comment

* Remove obsolete code

* Update tests

* Add clarifying comment to setting of responsive image src attribute

* Update clarifying comment on 1px bug in iOS

* Add namespace to state

---------

Co-authored-by: David Arenas <[email protected]>
Co-authored-by: Mario Santos <[email protected]>
Co-authored-by: Carlos Bravo <[email protected]>
  • Loading branch information
4 people authored Aug 14, 2023
1 parent 20bf292 commit 676f259
Show file tree
Hide file tree
Showing 4 changed files with 547 additions and 377 deletions.
86 changes: 63 additions & 23 deletions lib/block-supports/behaviors.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {

$aria_label = __( 'Enlarge image', 'gutenberg' );

$alt_attribute = trim( $processor->get_attribute( 'alt' ) );
$alt_attribute = $processor->get_attribute( 'alt' );

if ( null !== $alt_attribute ) {
$alt_attribute = trim( $alt_attribute );
}

if ( $alt_attribute ) {
/* translators: %s: Image alt text. */
Expand All @@ -89,22 +93,27 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
$z->next_tag( 'img' );

if ( isset( $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'] );
$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'];
} else {
$img_uploaded_src = $z->get_attribute( 'src' );
$img_width = 'none';
$img_height = 'none';
$img_uploaded_srcset = '';
$img_uploaded_src = $z->get_attribute( 'src' );
$img_width = 'none';
$img_height = 'none';
}

if ( isset( $block['attrs']['scale'] ) ) {
$scale_attr = $block['attrs']['scale'];
} else {
$scale_attr = false;
}

$w = new WP_HTML_Tag_Processor( $content );
$w->next_tag( 'figure' );
$w->add_class( 'wp-lightbox-container' );
$w->set_attribute( 'data-wp-interactive', true );

$w->set_attribute(
'data-wp-context',
sprintf(
Expand All @@ -118,47 +127,78 @@ function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) {
"lightboxAnimation": "%s",
"imageUploadedSrc": "%s",
"imageCurrentSrc": "",
"imageSrcSet": "%s",
"targetWidth": "%s",
"targetHeight": "%s"
"targetHeight": "%s",
"scaleAttr": "%s"
}
}
}',
$lightbox_animation,
$img_uploaded_src,
$img_uploaded_srcset,
$img_width,
$img_height
$img_height,
$scale_attr
)
);
$w->next_tag( 'img' );
$w->set_attribute( 'data-wp-effect', 'effects.core.image.setCurrentSrc' );
$w->set_attribute( 'data-wp-init', 'effects.core.image.setCurrentSrc' );
$w->set_attribute( 'data-wp-on--load', 'actions.core.image.handleLoad' );
$w->set_attribute( 'data-wp-effect', 'effects.core.image.setButtonStyles' );
$body_content = $w->get_updated_html();

// Wrap the image in the body content with a button.
$img = null;
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" data-wp-on--mouseenter="actions.core.image.preloadLightboxImage"></button>'
. $img[0] .
'</div>';
$button =
'<button
type="button"
aria-haspopup="dialog"
aria-label="' . esc_attr( $aria_label ) . '"
data-wp-on--click="actions.core.image.showLightbox"
data-wp-style--width="context.core.image.imageButtonWidth"
data-wp-style--height="context.core.image.imageButtonHeight"
data-wp-style--left="context.core.image.imageButtonLeft"
data-wp-style--top="context.core.image.imageButtonTop"
>
</button>'
. $img[0];
$body_content = preg_replace( '/<img[^>]+>/', $button, $body_content );

// Add src to the modal image.
// We need both a responsive image and an enlarged image to animate
// the zoom seamlessly on slow internet connections; the responsive
// image is a copy of the one in the body, which animates immediately
// as the lightbox is opened, while the enlarged one is a full-sized
// version that will likely still be loading as the animation begins.
$m = new WP_HTML_Tag_Processor( $content );
$m->next_tag( 'figure' );
$m->add_class( 'responsive-image' );
$m->next_tag( 'img' );
// We want to set the 'src' attribute to an empty string in the responsive image
// because otherwise, as of this writing, the wp_filter_content_tags() function in
// WordPress will automatically add a 'srcset' attribute to the image, which will at
// times cause the incorrectly sized image to be loaded in the lightbox on Firefox.
// Because of this, we bind the 'src' attribute explicitly the current src to reliably
// use the exact same image as in the content when the lightbox is first opened while
// we wait for the larger image to load.
$m->set_attribute( 'src', '' );
$m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.responsiveImgSrc' );
$m->set_attribute( 'data-wp-bind--src', 'context.core.image.imageCurrentSrc' );
$m->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' );
$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' );

// We set the 'src' attribute to an empty string to prevent the browser from loading the image
// on initial page load, then bind the attribute to a selector that returns the full-sized image src when
// the lightbox is opened. We could use 'loading=lazy' in combination with the 'hidden' attribute to
// accomplish the same behavior, but that approach breaks progressive loading of the image in Safari
// and Chrome (see https://github.com/WordPress/gutenberg/pull/52765#issuecomment-1674008151). Until that
// is resolved, manually setting the 'src' seems to be the best solution to load the large image on demand.
$q->set_attribute( 'src', '' );
$q->set_attribute( 'data-wp-bind--src', 'selectors.core.image.enlargedImgSrc' );
$q->set_attribute( 'data-wp-style--object-fit', 'selectors.core.image.lightboxObjectFit' );
$enlarged_image_content = $q->get_updated_html();

$background_color = esc_attr( wp_get_global_styles( array( 'color', 'background' ) ) );
Expand All @@ -185,8 +225,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>
$initial_image_content
$enlarged_image_content
<div class="lightbox-image-container">$initial_image_content</div>
<div class="lightbox-image-container">$enlarged_image_content</div>
<div class="scrim" style="background-color: $background_color"></div>
</div>
HTML;
Expand Down
156 changes: 72 additions & 84 deletions packages/block-library/src/image/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,7 @@
}

.wp-lightbox-container {

.img-container {
position: relative;
}
position: relative;

button {
border: none;
Expand All @@ -183,6 +180,7 @@
overflow: hidden;
width: 100vw;
height: 100vh;
box-sizing: border-box;
visibility: hidden;
cursor: zoom-out;

Expand All @@ -195,25 +193,39 @@
z-index: 5000000;
}

.lightbox-image-container {
position: absolute;
overflow: hidden;
top: 50%;
left: 50%;
transform-origin: top left;
transform: translate(-50%, -50%);
width: var(--wp--lightbox-container-width);
height: var(--wp--lightbox-container-height);
z-index: 9999999999;
}

.wp-block-image {
position: relative;
transform-origin: 0 0;
display: flex;
width: 100%;
height: 100%;
position: absolute;
z-index: 3000000;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
box-sizing: border-box;
z-index: 3000000;
margin: 0;

figcaption {
display: none;
img {
min-width: var(--wp--lightbox-image-width);
min-height: var(--wp--lightbox-image-height);
width: var(--wp--lightbox-image-width);
height: var(--wp--lightbox-image-height);
}

img {
max-width: 100%;
max-height: 100%;
width: auto;
figcaption {
display: none;
}
}

Expand All @@ -231,78 +243,61 @@
opacity: 0.9;
}

&.fade {
.wp-block-image {
padding: 40px 0;

@media screen and (min-width: 480px) {
padding: 40px;
}

@media screen and (min-width: 1920px) {
padding: 40px 80px;
}
// When fading, make the image come in slightly slower
// or faster than the scrim to give a sense of depth.
&.active {
visibility: visible;
animation: both turn-on-visibility 0.25s;
img {
animation: both turn-on-visibility 0.35s;
}

&.active {
visibility: visible;
animation: both turn-on-visibility 0.25s;

}
&.hideanimationenabled {
&:not(.active) {
animation: both turn-off-visibility 0.35s;
img {
animation: both turn-on-visibility 0.3s;
}
}
&.hideanimationenabled {
&:not(.active) {
animation: both turn-off-visibility 0.3s;

img {
animation: both turn-off-visibility 0.25s;
}
animation: both turn-off-visibility 0.25s;
}
}
}

&.zoom {
img {
position: absolute;
transform-origin: top left;
width: var(--lightbox-image-max-width);
height: var(--lightbox-image-max-height);
}

&.active {
opacity: 1;
visibility: visible;
.wp-block-image img {
animation: lightbox-zoom-in 0.4s forwards;

@media (prefers-reduced-motion) {
animation: both turn-on-visibility 0.4s;
}
}
.scrim {
animation: turn-on-visibility 0.4s forwards;
}
}
&.hideanimationenabled {
&:not(.active) {
.wp-block-image img {
animation: lightbox-zoom-out 0.4s forwards;

@media (prefers-reduced-motion) {
animation: both turn-off-visibility 0.4s;
@media (prefers-reduced-motion: no-preference) {
&.zoom {
&.active {
opacity: 1;
visibility: visible;
animation: none;
.lightbox-image-container {
animation: lightbox-zoom-in 0.4s;
// Override fade animation for image
img {
animation: none;
}
}
.scrim {
animation: turn-off-visibility 0.4s forwards;
animation: turn-on-visibility 0.4s forwards;
}
}
&.hideanimationenabled {
&:not(.active) {
animation: none;
.lightbox-image-container {
animation: lightbox-zoom-out 0.4s;
// Override fade animation for image
img {
animation: none;
}
}
.scrim {
animation: turn-off-visibility 0.4s forwards;
}
}
}
}
}
}

html.has-lightbox-open {
html.wp-has-lightbox-open {
overflow: hidden;
}

Expand Down Expand Up @@ -332,30 +327,23 @@ html.has-lightbox-open {

@keyframes lightbox-zoom-in {
0% {
left: var(--lightbox-initial-left-position);
top: var(--lightbox-initial-top-position);
transform: scale(var(--lightbox-scale-width), var(--lightbox-scale-height));
transform: translate(calc(-50vw + var(--wp--lightbox-initial-left-position)), calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale));
}
100% {
left: var(--lightbox-target-left-position);
top: var(--lightbox-target-top-position);
transform: scale(1, 1);
transform: translate(-50%, -50%) scale(1, 1);
}
}

@keyframes lightbox-zoom-out {
0% {
visibility: visible;
left: var(--lightbox-target-left-position);
top: var(--lightbox-target-top-position);
transform: scale(1, 1);
transform: translate(-50%, -50%) scale(1, 1);
}
99% {
visibility: visible;
}
100% {
left: var(--lightbox-initial-left-position);
top: var(--lightbox-initial-top-position);
transform: scale(var(--lightbox-scale-width), var(--lightbox-scale-height));
visibility: hidden;
transform: translate(calc(-50vw + var(--wp--lightbox-initial-left-position)), calc(-50vh + var(--wp--lightbox-initial-top-position))) scale(var(--wp--lightbox-scale));
}
}
Loading

0 comments on commit 676f259

Please sign in to comment.