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
101 changes: 71 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 sizerStyle: React.CSSProperties | undefined
let imgStyle: React.CSSProperties | undefined
let wrapperStyle: 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}%`,
if (layout === 'responsive') {
// <Image src="i.png" width="100" height="100" layout="responsive" />
const quotient = heightInt / widthInt
const paddingTop = isNaN(quotient) ? '100%' : `${quotient * 100}%`
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 = {
width: widthInt,
height: heightInt,
maxWidth: '100%',
}
} 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,25 @@ 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}></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="/test.jpg"
width="400"
height="400"
layout="fixed"
></Image>
<Image
id="fixed2"
src="/test.png"
width="400"
height="400"
layout="fixed"
></Image>
<Image
id="fixed3"
src="/test.png"
width="400"
height="400"
layout="fixed"
></Image>
<Image
id="fixed4"
src="/test.png"
width="400"
height="400"
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="/test.jpg"
width="400"
height="400"
layout="intrinsic"
></Image>
<Image
id="intrinsic2"
src="/test.png"
width="400"
height="400"
layout="intrinsic"
></Image>
<Image
id="intrinsic3"
src="/test.png"
width="400"
height="400"
layout="intrinsic"
></Image>
<Image
id="intrinsic4"
src="/test.png"
width="400"
height="400"
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="/test.jpg"
width="400"
height="400"
layout="responsive"
></Image>
<Image
id="responsive2"
src="/test.png"
width="400"
height="400"
layout="responsive"
></Image>
<Image
id="responsive3"
src="/test.png"
width="400"
height="400"
layout="responsive"
></Image>
<Image
id="responsive4"
src="/test.png"
width="400"
height="400"
layout="responsive"
></Image>
pacocoursey marked this conversation as resolved.
Show resolved Hide resolved
<p>Layout Responsive</p>
</div>
)
}

export default Page