diff --git a/CHANGELOG.md b/CHANGELOG.md index b4609714..c1a537d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `aspectRatio` and `maxHeight` on `ProductImage`. ## [2.52.3] - 2020-03-17 ### Added diff --git a/docs/ProductSummaryImage.md b/docs/ProductSummaryImage.md index 7517ec4c..f1646a4a 100644 --- a/docs/ProductSummaryImage.md +++ b/docs/ProductSummaryImage.md @@ -48,6 +48,8 @@ Through the Storefront, you can change the `ProductSummaryImage`'s behavior and | `mainImageLabel` | `String` | Works the same way as `hoverImageLabel` but to set the main image to display. If you pass a label and no image has that label, it will show the main image of the product | `""`| | `width` | `[ResponsiveInput](https://github.com/vtex-apps/responsive-values#vtexresponsive-values)` | Sets the image width. | `undefined` | | `height` | `[ResponsiveInput](https://github.com/vtex-apps/responsive-values#vtexresponsive-values)` | Sets the image height. | `undefined` | +| `aspectRatio` | `[ResponsiveInput](https://github.com/vtex-apps/responsive-values#vtexresponsive-values)` | Sets the aspect ratio of the image, that is, whether the image should be square, portrait, landscape, etc. The value should follow the [common aspect ratio notation](https://en.wikipedia.org/wiki/Aspect_ratio_(image)) i.e. two numbers separated by a colon such as `1:1` for square, `3:4` for upright portrait, or `1920:1080` for even large values). Note that this prop won't work if you use `width` or `height`. | `undefined` | +| `maxHeight` | `[ResponsiveInput](https://github.com/vtex-apps/responsive-values#vtexresponsive-values)` | Sets the image max height. Note that this prop won't work if you use `width` or `height`.| `undefined` | ### Styles API diff --git a/react/components/ProductSummaryImage/ProductImage.js b/react/components/ProductSummaryImage/ProductImage.js index 946c5b05..1672ac71 100644 --- a/react/components/ProductSummaryImage/ProductImage.js +++ b/react/components/ProductSummaryImage/ProductImage.js @@ -7,16 +7,47 @@ import { useDevice } from 'vtex.device-detector' import { useResponsiveValues } from 'vtex.responsive-values' import { useCssHandles, applyModifiers } from 'vtex.css-handles' import { useProduct } from 'vtex.product-context' - -import ImagePlaceholder from './ImagePlaceholder' - import { useProductSummary } from 'vtex.product-summary-context/ProductSummaryContext' +import ImagePlaceholder from './ImagePlaceholder' import productSummary from '../../productSummary.css' - import { changeImageUrlSize } from '../../utils/normalize' +import { imageUrl } from '../../utils/aspectRatioUtil' const CSS_HANDLES = ['image', 'imageContainer', 'product', 'imagePlaceholder'] +const MAX_SIZE = 500 +const DEFAULT_SIZE = 300 + +const getImageSrc = (src, width, height, dpi, aspectRatio) => { + if (width || height) { + return changeImageUrlSize(src, width * dpi, height * dpi) + } + if (aspectRatio) { + return imageUrl(src, DEFAULT_SIZE, MAX_SIZE, aspectRatio) + } + return src +} + +const getStyle = (width, height, aspectRatio, maxHeight) => { + if (width || height) { + return { + width: '100%', + height, + objectFit: 'contain', + maxHeight: 'unset', + maxWidth: width, + } + } + if (aspectRatio || maxHeight) { + return { + width: '100%', + height: '100%', + objectFit: 'contain', + maxHeight: maxHeight || 'unset', + } + } + return null +} const maybeBadge = ({ listPrice, price, label }) => shouldShow => component => { if (shouldShow) { @@ -48,7 +79,16 @@ const findImageByLabel = (images, selectedLabel) => { return images.find(({ imageLabel }) => imageLabel === selectedLabel) } -const Image = ({ src, width, height, onError, alt, className }) => { +const Image = ({ + src, + width, + height, + onError, + alt, + className, + aspectRatio, + maxHeight, +}) => { const { isMobile } = useDevice() /** TODO: Previously it was as follows : @@ -64,20 +104,8 @@ const Image = ({ src, width, height, onError, alt, className }) => { return ( {alt} { const handles = useCssHandles(CSS_HANDLES) const { isMobile } = useDevice() @@ -180,6 +210,8 @@ const ProductImageContent = ({ src={imageUrl} width={width} height={height} + aspectRatio={aspectRatio} + maxHeight={maxHeight} alt={name} className={imageClassname} onError={onError} @@ -189,6 +221,8 @@ const ProductImageContent = ({ src={hoverImage.imageUrl} width={width} height={height} + aspectRatio={aspectRatio} + maxHeight={maxHeight} alt={name} className={hoverImageClassname} onError={onError} @@ -212,12 +246,21 @@ const ProductImage = ({ showCollections, width: widthProp, height: heightProp, + aspectRatio: aspectRatioProp, + maxHeight: maxHeightProp, }) => { const { product } = useProductSummary() - const { widthProp: width, heightProp: height } = useResponsiveValues({ + const { + widthProp: width, + heightProp: height, + aspectRatioProp: aspectRatio, + maxHeightProp: maxHeight, + } = useResponsiveValues({ widthProp, heightProp, + aspectRatioProp, + maxHeightProp, }) const [error, setError] = useState(false) @@ -231,6 +274,8 @@ const ProductImage = ({ { + if (!input) { + return null + } + if (typeof input === 'string') { + if (input === 'auto') { + return null + } + const separator = ':' + const data = input.split(separator) + if (data.length !== 2) { + return null + } + + const [width, height] = data + const ratio = parseFloat(height) / parseFloat(width) + + if (typeof ratio !== 'number' || isNaN(ratio)) { + return null + } + + return ratio + } + + if (typeof input === 'number') { + return input + } + + return null +} + +export const imageUrl = (src, size, maxSize, aspectRatio) => { + let width = size + let height = 'auto' + + if (aspectRatio && aspectRatio !== 'auto') { + height = size * (parseAspectRatio(aspectRatio) || 1) + + if (width > maxSize) { + height = height / (width / maxSize) + width = maxSize + } + + if (height > maxSize) { + width = width / (height / maxSize) + height = maxSize + } + + width = Math.round(width) + height = Math.round(height) + } else { + width = Math.min(maxSize, width) + } + return changeImageUrlSize(src, width, height) +}