diff --git a/extensions/blocks/tiled-gallery/gallery-image/edit.js b/extensions/blocks/tiled-gallery/gallery-image/edit.js
index e4d77349531ab..1658873a5101e 100644
--- a/extensions/blocks/tiled-gallery/gallery-image/edit.js
+++ b/extensions/blocks/tiled-gallery/gallery-image/edit.js
@@ -68,6 +68,7 @@ class GalleryImageEdit extends Component {
linkTo,
onRemove,
origUrl,
+ srcSet,
url,
width,
} = this.props;
@@ -102,6 +103,7 @@ class GalleryImageEdit extends Component {
onKeyDown={ this.onImageKeyDown }
ref={ this.img }
src={ isTransient ? undefined : url }
+ srcSet={ isTransient ? undefined : srcSet }
tabIndex="0"
style={ isTransient ? { backgroundImage: `url(${ url })` } : undefined }
/>
diff --git a/extensions/blocks/tiled-gallery/layout/index.js b/extensions/blocks/tiled-gallery/layout/index.js
index abcb564109d9e..58feff8fc304a 100644
--- a/extensions/blocks/tiled-gallery/layout/index.js
+++ b/extensions/blocks/tiled-gallery/layout/index.js
@@ -1,11 +1,8 @@
/**
* External dependencies
*/
-import photon from 'photon';
import { __, sprintf } from '@wordpress/i18n';
import { Component } from '@wordpress/element';
-import { format as formatUrl, parse as parseUrl } from 'url';
-import { isBlobURL } from '@wordpress/blob';
/**
* Internal dependencies
@@ -14,34 +11,9 @@ import GalleryImageEdit from '../gallery-image/edit';
import GalleryImageSave from '../gallery-image/save';
import Mosaic from './mosaic';
import Square from './square';
-import { PHOTON_MAX_RESIZE } from '../constants';
+import { isSquareishLayout, photonizedImgProps } from '../utils';
export default class Layout extends Component {
- photonize( { height, width, url } ) {
- if ( ! url ) {
- return;
- }
-
- // Do not Photonize images that are still uploading or from localhost
- if ( isBlobURL( url ) || /^https?:\/\/localhost/.test( url ) ) {
- return url;
- }
-
- // Drop query args, photon URLs can't handle them
- // This should be the "raw" url, we'll add dimensions later
- const cleanUrl = url.split( '?', 1 )[ 0 ];
-
- const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon;
-
- const { layoutStyle } = this.props;
-
- if ( isSquareishLayout( layoutStyle ) && width && height ) {
- const size = Math.min( PHOTON_MAX_RESIZE, width, height );
- return photonImplementation( cleanUrl, { resize: `${ size },${ size }` } );
- }
- return photonImplementation( cleanUrl );
- }
-
// This is tricky:
// - We need to "photonize" to resize the images at appropriate dimensions
// - The resize will depend on the image size and the layout in some cases
@@ -53,6 +25,7 @@ export default class Layout extends Component {
images,
isSave,
linkTo,
+ layoutStyle,
onRemoveImage,
onSelectImage,
selectedImage,
@@ -67,6 +40,8 @@ export default class Layout extends Component {
);
const Image = isSave ? GalleryImageSave : GalleryImageEdit;
+ const { src, srcSet } = photonizedImgProps( img, { layoutStyle } );
+
return (
);
@@ -109,52 +85,3 @@ export default class Layout extends Component {
);
}
}
-
-function isSquareishLayout( layout ) {
- return [ 'circle', 'square' ].includes( layout );
-}
-
-function isWpcomFilesUrl( url ) {
- const { host } = parseUrl( url );
- return /\.files\.wordpress\.com$/.test( host );
-}
-
-/**
- * Apply photon arguments to *.files.wordpress.com images
- *
- * This function largely duplicates the functionlity of the photon.js lib.
- * This is necessary because we want to serve images from *.files.wordpress.com so that private
- * WordPress.com sites can use this block which depends on a Photon-like image service.
- *
- * If we pass all images through Photon servers, some images are unreachable. *.files.wordpress.com
- * is already photon-like so we can pass it the same parameters for image resizing.
- *
- * @param {string} url Image url
- * @param {Object} opts Options to pass to photon
- *
- * @return {string} Url string with options applied
- */
-function photonWpcomImage( url, opts = {} ) {
- // Adhere to the same options API as the photon.js lib
- const photonLibMappings = {
- width: 'w',
- height: 'h',
- letterboxing: 'lb',
- removeLetterboxing: 'ulb',
- };
-
- // Discard some param parts
- const { auth, hash, port, query, search, ...urlParts } = parseUrl( url );
-
- // Build query
- // This reduction intentionally mutates the query as it is built internally.
- urlParts.query = Object.keys( opts ).reduce(
- ( q, key ) =>
- Object.assign( q, {
- [ photonLibMappings.hasOwnProperty( key ) ? photonLibMappings[ key ] : key ]: opts[ key ],
- } ),
- {}
- );
-
- return formatUrl( urlParts );
-}
diff --git a/extensions/blocks/tiled-gallery/utils/index.js b/extensions/blocks/tiled-gallery/utils/index.js
new file mode 100644
index 0000000000000..1fbb4bb44352f
--- /dev/null
+++ b/extensions/blocks/tiled-gallery/utils/index.js
@@ -0,0 +1,153 @@
+/**
+ * External dependencies
+ */
+import photon from 'photon';
+import { format as formatUrl, parse as parseUrl } from 'url';
+import { isBlobURL } from '@wordpress/blob';
+import { range } from 'lodash';
+
+/**
+ * Internal dependencies
+ */
+import { PHOTON_MAX_RESIZE } from '../constants';
+
+export function isSquareishLayout( layout ) {
+ return [ 'circle', 'square' ].includes( layout );
+}
+
+/**
+ * Build src and srcSet properties which can be used on an
+ *
+ * @param {Object} img Image
+ * @param {number} img.height Image height
+ * @param {string} img.url Image URL
+ * @param {number} img.width Image width
+ *
+ * @param {Object} galleryAtts Gallery attributes relevant for image optimization.
+ * @param {string} galleryAtts.layoutStyle Gallery layout. 'rectangular', 'circle', etc.
+ * @param {number} galleryAtts.columns Gallery columns. Not applicable for all layouts.
+ *
+ * @return {Object} Returns an object. If possible, the object will include `src` and `srcSet`
+ * properties {string} for use on an image.
+ */
+export function photonizedImgProps( img, galleryAtts = {} ) {
+ if ( ! img.height || ! img.url || ! img.width ) {
+ return {};
+ }
+
+ // Do not Photonize images that are still uploading or from localhost
+ if ( isBlobURL( img.url ) || /^https?:\/\/localhost/.test( img.url ) ) {
+ return { src: img.url };
+ }
+
+ // Drop query args, photon URLs can't handle them
+ // This should be the "raw" url, we'll add dimensions later
+ const url = img.url.split( '?', 1 )[ 0 ];
+ const { height, width } = img;
+ const { layoutStyle } = galleryAtts;
+
+ const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon;
+
+ /**
+ * Build the `src`
+ * We don't know what the viewport size will be like. Use full size src.
+ */
+
+ let src;
+ if ( isSquareishLayout( layoutStyle ) && width && height ) {
+ // Layouts with 1:1 width/height ratio should be made square
+ const size = Math.min( PHOTON_MAX_RESIZE, width, height );
+ src = photonImplementation( url, {
+ resize: `${ size },${ size }`,
+ strip: 'all',
+ } );
+ } else {
+ src = photonImplementation( url, { strip: 'all' } );
+ }
+
+ /**
+ * Build a sensible `srcSet` that will let the browser get an optimized image based on
+ * viewport width.
+ */
+
+ const step = 300;
+ const srcsetMinWith = 600;
+
+ let srcSet;
+ if ( isSquareishLayout( layoutStyle ) ) {
+ const minWidth = Math.min( srcsetMinWith, width, height );
+ const maxWidth = Math.min( PHOTON_MAX_RESIZE, width, height );
+
+ srcSet = range( minWidth, maxWidth, step )
+ .map( srcsetWidth => {
+ const srcsetSrc = photonImplementation( url, {
+ resize: `${ srcsetWidth },${ srcsetWidth }`,
+ strip: 'all',
+ } );
+ return srcsetSrc ? `${ srcsetSrc } ${ srcsetWidth }w` : null;
+ } )
+ .filter( Boolean )
+ .join( ',' );
+ } else {
+ const minWidth = Math.min( srcsetMinWith, width );
+ const maxWidth = Math.min( PHOTON_MAX_RESIZE, width );
+
+ srcSet = range( minWidth, maxWidth, step )
+ .map( srcsetWidth => {
+ const srcsetSrc = photonImplementation( url, {
+ strip: 'all',
+ width: srcsetWidth,
+ } );
+ return srcsetSrc ? `${ srcsetSrc } ${ srcsetWidth }w` : null;
+ } )
+ .filter( Boolean )
+ .join( ',' );
+ }
+
+ return Object.assign( { src }, srcSet && { srcSet } );
+}
+
+function isWpcomFilesUrl( url ) {
+ const { host } = parseUrl( url );
+ return /\.files\.wordpress\.com$/.test( host );
+}
+
+/**
+ * Apply photon arguments to *.files.wordpress.com images
+ *
+ * This function largely duplicates the functionlity of the photon.js lib.
+ * This is necessary because we want to serve images from *.files.wordpress.com so that private
+ * WordPress.com sites can use this block which depends on a Photon-like image service.
+ *
+ * If we pass all images through Photon servers, some images are unreachable. *.files.wordpress.com
+ * is already photon-like so we can pass it the same parameters for image resizing.
+ *
+ * @param {string} url Image url
+ * @param {Object} opts Options to pass to photon
+ *
+ * @return {string} Url string with options applied
+ */
+function photonWpcomImage( url, opts = {} ) {
+ // Adhere to the same options API as the photon.js lib
+ const photonLibMappings = {
+ width: 'w',
+ height: 'h',
+ letterboxing: 'lb',
+ removeLetterboxing: 'ulb',
+ };
+
+ // Discard some param parts
+ const { auth, hash, port, query, search, ...urlParts } = parseUrl( url );
+
+ // Build query
+ // This reduction intentionally mutates the query as it is built internally.
+ urlParts.query = Object.keys( opts ).reduce(
+ ( q, key ) =>
+ Object.assign( q, {
+ [ photonLibMappings.hasOwnProperty( key ) ? photonLibMappings[ key ] : key ]: opts[ key ],
+ } ),
+ {}
+ );
+
+ return formatUrl( urlParts );
+}