Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add layout prop to Image component #18491

Merged
merged 11 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-reference/next/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default Home
- `src` - The path or URL to the source image. This is required.
- `width` - The intrinsic width of the source image in pixels. Must be an integer without a unit. Required unless `unsized` is true.
- `height` - The intrinsic height of the source image, in pixels. Must be an integer without a unit. Required unless `unsized` is true.
- `layout` - The rendered layout of the image. If `fixed`, the image dimensions will not change as the viewport changes (no responsiveness). If `intrinsic`, the image will scale the dimensions down for smaller viewports but maintain the original dimensions for larger viewports. If `responsive`, the image will scale the dimensions down for smaller viewports and scale up for larger viewports. Default `intrinsic`.
- `sizes` - Defines what proportion of the screen you expect the image to take up. Recommended, as it helps serve the correct sized image to each device. [More info](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-sizes).
- `quality` - The quality of the optimized image, an integer between 1 and 100 where 100 is the best quality. Default 75.
- `loading` - The loading behavior. When `lazy`, defer loading the image until it reaches a calculated distance from the viewport. When `eager`, load the image immediately. Default `lazy`. [More info](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading)
Expand Down
113 changes: 83 additions & 30 deletions packages/next/client/image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ const loaders = new Map<LoaderKey, (props: LoaderProps) => string>([

type LoaderKey = 'imgix' | 'cloudinary' | 'akamai' | 'default'

const VALID_LAYOUT_VALUES = [
'fixed',
'intrinsic',
'responsive',
undefined,
] as const
type LayoutValue = typeof VALID_LAYOUT_VALUES[number]

type ImageData = {
deviceSizes: number[]
imageSizes: number[]
Expand All @@ -29,6 +37,7 @@ type ImageProps = Omit<
quality?: number | string
priority?: boolean
loading?: LoadingValue
layout?: LayoutValue
unoptimized?: boolean
} & (
| { width: number | string; height: number | string; unsized?: false }
Expand Down Expand Up @@ -201,6 +210,7 @@ export default function Image({
unoptimized = false,
priority = false,
loading,
layout,
className,
quality,
width,
Expand All @@ -218,6 +228,13 @@ export default function Image({
)}`
)
}
if (!VALID_LAYOUT_VALUES.includes(layout)) {
throw new Error(
`Image with src "${src}" has invalid "layout" property. Provided "${layout}" should be one of ${VALID_LAYOUT_VALUES.map(
String
).join(',')}.`
)
}
if (!VALID_LOADING_VALUES.includes(loading)) {
throw new Error(
`Image with src "${src}" has invalid "loading" property. Provided "${loading}" should be one of ${VALID_LOADING_VALUES.map(
Expand All @@ -232,6 +249,14 @@ export default function Image({
}
}

if (!layout) {
if (sizes) {
layout = 'responsive'
} else {
layout = 'intrinsic'
styfle marked this conversation as resolved.
Show resolved Hide resolved
}
}

let lazy = loading === 'lazy'
if (!priority && typeof loading === 'undefined') {
lazy = true
Expand Down Expand Up @@ -265,25 +290,41 @@ export default function Image({
const heightInt = getInt(height)
const qualityInt = getInt(quality)

let divStyle: React.CSSProperties | undefined
let imgStyle: React.CSSProperties | undefined
let wrapperStyle: React.CSSProperties | undefined
let sizerStyle: React.CSSProperties | undefined
let sizerSvg: string | undefined
let imgStyle: React.CSSProperties | undefined
if (
typeof widthInt !== 'undefined' &&
typeof heightInt !== 'undefined' &&
!unsized
) {
// <Image src="i.png" width={100} height={100} />
// <Image src="i.png" width="100" height="100" />
const quotient = heightInt / widthInt
const ratio = isNaN(quotient) ? 1 : quotient * 100
wrapperStyle = {
maxWidth: '100%',
width: widthInt,
}
divStyle = {
position: 'relative',
paddingBottom: `${ratio}%`,
const paddingTop = isNaN(quotient) ? '100%' : `${quotient * 100}%`
if (layout === 'responsive') {
// <Image src="i.png" width="100" height="100" layout="responsive" />
wrapperStyle = { position: 'relative' }
sizerStyle = { paddingTop }
} else if (layout === 'intrinsic') {
// <Image src="i.png" width="100" height="100" layout="intrinsic" />
wrapperStyle = {
display: 'inline-block',
position: 'relative',
maxWidth: '100%',
}
sizerStyle = {
maxWidth: '100%',
}
sizerSvg = `<svg width="${widthInt}" height="${heightInt}" xmlns="http://www.w3.org/2000/svg" version="1.1"/>`
} else if (layout === 'fixed') {
// <Image src="i.png" width="100" height="100" layout="fixed" />
wrapperStyle = {
display: 'inline-block',
position: 'relative',
width: widthInt,
height: heightInt,
}
}
imgStyle = {
visibility: lazy ? 'hidden' : 'visible',
Expand Down Expand Up @@ -357,25 +398,37 @@ export default function Image({

return (
<div style={wrapperStyle}>
<div style={divStyle}>
{shouldPreload
? generatePreload({
src,
width: widthInt,
unoptimized,
sizes,
quality: qualityInt,
})
: ''}
<img
{...rest}
{...imgAttributes}
className={className}
sizes={sizes}
ref={thisEl}
style={imgStyle}
/>
</div>
{shouldPreload
? generatePreload({
src,
width: widthInt,
unoptimized,
sizes,
quality: qualityInt,
})
: null}
{sizerStyle ? (
<div style={sizerStyle}>
{sizerSvg ? (
<img
style={{ maxWidth: '100%', display: 'block' }}
alt=""
aria-hidden={true}
role="presentation"
src={`data:image/svg+xml;charset=utf-8,${sizerSvg}`}
/>
) : null}
</div>
) : null}
<img
{...rest}
{...imgAttributes}
decoding="async"
className={className}
sizes={sizes}
ref={thisEl}
style={imgStyle}
/>
</div>
)
}
Expand Down
41 changes: 41 additions & 0 deletions test/integration/image-component/default/pages/layout-fixed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import Image from 'next/image'

const Page = () => {
return (
<div>
<p>Layout Fixed</p>
<Image
id="fixed1"
src="/wide.png"
width="1200"
height="700"
layout="fixed"
></Image>
<Image
id="fixed2"
src="/wide.png"
width="1200"
height="700"
layout="fixed"
></Image>
<Image
id="fixed3"
src="/wide.png"
width="1200"
height="700"
layout="fixed"
></Image>
<Image
id="fixed4"
src="/wide.png"
width="1200"
height="700"
layout="fixed"
></Image>
<p>Layout Fixed</p>
</div>
)
}

export default Page
41 changes: 41 additions & 0 deletions test/integration/image-component/default/pages/layout-intrinsic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import Image from 'next/image'

const Page = () => {
return (
<div>
<p>Layout Intrinsic</p>
<Image
id="intrinsic1"
src="/wide.png"
width="1200"
height="700"
layout="intrinsic"
></Image>
<Image
id="intrinsic2"
src="/wide.png"
width="1200"
height="700"
layout="intrinsic"
></Image>
<Image
id="intrinsic3"
src="/wide.png"
width="1200"
height="700"
layout="intrinsic"
></Image>
<Image
id="intrinsic4"
src="/wide.png"
width="1200"
height="700"
layout="intrinsic"
></Image>
<p>Layout Intrinsic</p>
</div>
)
}

export default Page
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react'
import Image from 'next/image'

const Page = () => {
return (
<div>
<p>Layout Responsive</p>
<Image
id="responsive1"
src="/wide.png"
width="1200"
height="700"
layout="responsive"
></Image>
<Image
id="responsive2"
src="/wide.png"
width="1200"
height="700"
layout="responsive"
></Image>
<Image
id="responsive3"
src="/wide.png"
width="1200"
height="700"
layout="responsive"
></Image>
<Image
id="responsive4"
src="/wide.png"
width="1200"
height="700"
layout="responsive"
></Image>
pacocoursey marked this conversation as resolved.
Show resolved Hide resolved
<p>Layout Responsive</p>
</div>
)
}

export default Page
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading