2020-11-12 20:45:50 +01:00
import React from 'react'
2021-06-30 11:43:31 +02:00
import Head from '../shared/lib/head'
import { toBase64 } from '../shared/lib/to-base-64'
2020-11-12 20:24:08 +01:00
import {
2021-09-09 00:03:52 +02:00
ImageConfigComplete ,
2020-11-12 20:24:08 +01:00
imageConfigDefault ,
LoaderValue ,
VALID_LOADERS ,
2021-06-30 13:44:40 +02:00
} from '../server/image-config'
2020-11-07 00:03:15 +01:00
import { useIntersection } from './use-intersection'
2020-10-14 11:57:10 +02:00
2021-07-07 19:51:16 +02:00
const loadedImageURLs = new Set < string > ( )
2021-09-26 17:03:07 +02:00
const emptyDataURL =
'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'
2021-07-07 19:51:16 +02:00
2020-11-11 16:46:48 +01:00
if ( typeof window === 'undefined' ) {
; ( global as any ) . __NEXT_IMAGE_IMPORTED = true
}
2020-10-22 20:59:42 +02:00
const VALID_LOADING_VALUES = [ 'lazy' , 'eager' , undefined ] as const
type LoadingValue = typeof VALID_LOADING_VALUES [ number ]
2021-01-05 21:51:34 +01:00
export type ImageLoader = ( resolverProps : ImageLoaderProps ) = > string
export type ImageLoaderProps = {
src : string
width : number
quality? : number
}
type DefaultImageLoaderProps = ImageLoaderProps & { root : string }
const loaders = new Map <
LoaderValue ,
( props : DefaultImageLoaderProps ) = > string
> ( [
2021-07-08 21:35:19 +02:00
[ 'default' , defaultLoader ] ,
2020-10-21 21:39:12 +02:00
[ 'imgix' , imgixLoader ] ,
[ 'cloudinary' , cloudinaryLoader ] ,
2020-10-21 21:55:02 +02:00
[ 'akamai' , akamaiLoader ] ,
2021-07-08 21:35:19 +02:00
[ 'custom' , customLoader ] ,
2020-10-21 21:39:12 +02:00
] )
2020-10-30 15:33:34 +01:00
const VALID_LAYOUT_VALUES = [
2020-10-31 23:34:06 +01:00
'fill' ,
2020-10-30 15:33:34 +01:00
'fixed' ,
'intrinsic' ,
'responsive' ,
undefined ,
] as const
type LayoutValue = typeof VALID_LAYOUT_VALUES [ number ]
2021-04-30 19:05:03 +02:00
type PlaceholderValue = 'blur' | 'empty'
2021-08-03 01:14:38 +02:00
type OnLoadingComplete = ( result : {
naturalWidth : number
naturalHeight : number
} ) = > void
2020-11-05 20:42:55 +01:00
type ImgElementStyle = NonNullable < JSX.IntrinsicElements [ ' img ' ] [ ' style ' ] >
2021-06-04 10:06:00 +02:00
interface StaticRequire {
default : StaticImageData
}
type StaticImport = StaticRequire | StaticImageData
function isStaticRequire (
src : StaticRequire | StaticImageData
) : src is StaticRequire {
return ( src as StaticRequire ) . default !== undefined
}
function isStaticImageData (
src : StaticRequire | StaticImageData
) : src is StaticImageData {
return ( src as StaticImageData ) . src !== undefined
}
function isStaticImport ( src : string | StaticImport ) : src is StaticImport {
return (
typeof src === 'object' &&
( isStaticRequire ( src as StaticImport ) ||
isStaticImageData ( src as StaticImport ) )
)
}
2020-11-09 07:20:54 +01:00
export type ImageProps = Omit <
2020-10-18 12:41:25 +02:00
JSX . IntrinsicElements [ 'img' ] ,
2020-11-04 17:13:07 +01:00
'src' | 'srcSet' | 'ref' | 'width' | 'height' | 'loading' | 'style'
2020-10-18 12:41:25 +02:00
> & {
2021-07-07 23:31:30 +02:00
src : string | StaticImport
width? : number | string
height? : number | string
layout? : LayoutValue
2021-01-05 21:51:34 +01:00
loader? : ImageLoader
2020-10-27 12:37:55 +01:00
quality? : number | string
2020-10-16 22:04:39 +02:00
priority? : boolean
2020-10-22 20:59:42 +02:00
loading? : LoadingValue
2021-07-23 16:44:38 +02:00
lazyBoundary? : string
2021-07-07 23:31:30 +02:00
placeholder? : PlaceholderValue
blurDataURL? : string
2020-10-16 22:04:39 +02:00
unoptimized? : boolean
2020-11-05 20:42:55 +01:00
objectFit? : ImgElementStyle [ 'objectFit' ]
objectPosition? : ImgElementStyle [ 'objectPosition' ]
2021-08-03 01:14:38 +02:00
onLoadingComplete? : OnLoadingComplete
2021-07-07 23:31:30 +02:00
}
2020-10-14 11:57:10 +02:00
2020-10-25 06:22:47 +01:00
const {
2020-10-26 21:07:52 +01:00
deviceSizes : configDeviceSizes ,
2020-10-27 14:19:23 +01:00
imageSizes : configImageSizes ,
2020-10-25 06:22:47 +01:00
loader : configLoader ,
path : configPath ,
domains : configDomains ,
2021-09-09 00:03:52 +02:00
} = ( process . env . __NEXT_IMAGE_OPTS as any as ImageConfigComplete ) ||
imageConfigDefault
2020-10-26 21:07:52 +01:00
// sort smallest to largest
2020-11-03 03:12:46 +01:00
const allSizes = [ . . . configDeviceSizes , . . . configImageSizes ]
2020-10-26 21:07:52 +01:00
configDeviceSizes . sort ( ( a , b ) = > a - b )
2020-11-03 03:12:46 +01:00
allSizes . sort ( ( a , b ) = > a - b )
2020-10-14 11:57:10 +02:00
2020-11-13 05:30:41 +01:00
function getWidths (
2020-11-01 01:26:57 +01:00
width : number | undefined ,
2021-02-24 23:57:19 +01:00
layout : LayoutValue ,
sizes : string | undefined
2020-11-13 05:30:41 +01:00
) : { widths : number [ ] ; kind : 'w' | 'x' } {
2021-02-24 23:57:19 +01:00
if ( sizes && ( layout === 'fill' || layout === 'responsive' ) ) {
// Find all the "vw" percent sizes used in the sizes prop
2021-04-29 12:07:27 +02:00
const viewportWidthRe = /(^|\s)(1?\d?\d)vw/g
const percentSizes = [ ]
for ( let match ; ( match = viewportWidthRe . exec ( sizes ) ) ; match ) {
percentSizes . push ( parseInt ( match [ 2 ] ) )
}
2021-02-24 23:57:19 +01:00
if ( percentSizes . length ) {
const smallestRatio = Math . min ( . . . percentSizes ) * 0.01
return {
widths : allSizes.filter (
( s ) = > s >= configDeviceSizes [ 0 ] * smallestRatio
) ,
kind : 'w' ,
}
}
return { widths : allSizes , kind : 'w' }
}
2020-11-01 01:26:57 +01:00
if (
typeof width !== 'number' ||
layout === 'fill' ||
layout === 'responsive'
) {
2020-11-13 05:30:41 +01:00
return { widths : configDeviceSizes , kind : 'w' }
2020-10-26 21:07:52 +01:00
}
2020-11-03 03:12:46 +01:00
2020-11-13 05:30:41 +01:00
const widths = [
2020-11-03 03:12:46 +01:00
. . . new Set (
2020-12-30 19:17:46 +01:00
// > This means that most OLED screens that say they are 3x resolution,
// > are actually 3x in the green color, but only 1.5x in the red and
// > blue colors. Showing a 3x resolution image in the app vs a 2x
// > resolution image will be visually the same, though the 3x image
// > takes significantly more data. Even true 3x resolution screens are
// > wasteful as the human eye cannot see that level of detail without
// > something like a magnifying glass.
// https://blog.twitter.com/engineering/en_us/topics/infrastructure/2019/capping-image-fidelity-on-ultra-high-resolution-devices.html
[ width , width * 2 /*, width * 3*/ ] . map (
2020-11-03 03:12:46 +01:00
( w ) = > allSizes . find ( ( p ) = > p >= w ) || allSizes [ allSizes . length - 1 ]
)
) ,
]
2020-11-13 05:30:41 +01:00
return { widths , kind : 'x' }
2020-10-14 11:57:10 +02:00
}
2020-11-13 05:30:41 +01:00
type GenImgAttrsData = {
2020-10-14 11:57:10 +02:00
src : string
2020-10-26 15:29:52 +01:00
unoptimized : boolean
2020-11-01 01:26:57 +01:00
layout : LayoutValue
2021-01-05 21:51:34 +01:00
loader : ImageLoader
2020-10-27 12:37:55 +01:00
width? : number
quality? : number
2020-11-13 05:30:41 +01:00
sizes? : string
2020-10-14 11:57:10 +02:00
}
2020-12-31 22:08:57 +01:00
type GenImgAttrsResult = {
src : string
srcSet : string | undefined
sizes : string | undefined
}
2020-11-13 05:30:41 +01:00
function generateImgAttrs ( {
2020-10-26 15:29:52 +01:00
src ,
unoptimized ,
2020-11-01 01:26:57 +01:00
layout ,
2020-10-26 15:29:52 +01:00
width ,
quality ,
2020-11-13 05:30:41 +01:00
sizes ,
2021-01-05 21:51:34 +01:00
loader ,
2020-11-13 05:30:41 +01:00
} : GenImgAttrsData ) : GenImgAttrsResult {
2020-10-26 15:29:52 +01:00
if ( unoptimized ) {
2020-12-31 22:08:57 +01:00
return { src , srcSet : undefined , sizes : undefined }
2020-10-26 15:29:52 +01:00
}
2020-10-26 21:07:52 +01:00
2021-02-24 23:57:19 +01:00
const { widths , kind } = getWidths ( width , layout , sizes )
2020-11-13 05:30:41 +01:00
const last = widths . length - 1
2021-01-05 21:51:34 +01:00
return {
sizes : ! sizes && kind === 'w' ? '100vw' : sizes ,
srcSet : widths
. map (
( w , i ) = >
2021-06-09 00:05:02 +02:00
` ${ loader ( { src , quality , width : w } )} ${
2021-01-05 21:51:34 +01:00
kind === 'w' ? w : i + 1
} $ { kind } `
)
. join ( ', ' ) ,
2021-03-09 20:07:01 +01:00
// It's intended to keep `src` the last attribute because React updates
// attributes in order. If we keep `src` the first one, Safari will
// immediately start to fetch `src`, before `sizes` and `srcSet` are even
// updated by React. That causes multiple unnecessary requests if `srcSet`
// and `sizes` are defined.
// This bug cannot be reproduced in Chrome or Firefox.
2021-06-09 00:05:02 +02:00
src : loader ( { src , quality , width : widths [ last ] } ) ,
2020-11-13 05:30:41 +01:00
}
2020-10-14 11:57:10 +02:00
}
2020-10-26 15:29:52 +01:00
function getInt ( x : unknown ) : number | undefined {
if ( typeof x === 'number' ) {
return x
}
if ( typeof x === 'string' ) {
return parseInt ( x , 10 )
}
return undefined
}
2021-01-05 21:51:34 +01:00
function defaultImageLoader ( loaderProps : ImageLoaderProps ) {
const load = loaders . get ( configLoader )
if ( load ) {
return load ( { root : configPath , . . . loaderProps } )
}
throw new Error (
` Unknown "loader" found in "next.config.js". Expected: ${ VALID_LOADERS . join (
', '
) } . Received : $ { configLoader } `
)
}
2021-04-30 19:05:03 +02:00
// See https://stackoverflow.com/q/39777833/266535 for why we use this ref
// handler instead of the img's onLoad attribute.
2021-07-01 20:51:20 +02:00
function handleLoading (
2021-06-10 23:45:54 +02:00
img : HTMLImageElement | null ,
2021-07-07 19:51:16 +02:00
src : string ,
2021-08-18 04:05:10 +02:00
layout : LayoutValue ,
2021-07-01 20:51:20 +02:00
placeholder : PlaceholderValue ,
2021-08-03 01:14:38 +02:00
onLoadingComplete? : OnLoadingComplete
2021-04-30 19:05:03 +02:00
) {
2021-07-01 20:51:20 +02:00
if ( ! img ) {
return
}
const handleLoad = ( ) = > {
2021-09-26 17:03:07 +02:00
if ( img . src !== emptyDataURL ) {
2021-07-01 20:51:20 +02:00
const p = 'decode' in img ? img . decode ( ) : Promise . resolve ( )
p . catch ( ( ) = > { } ) . then ( ( ) = > {
if ( placeholder === 'blur' ) {
2021-06-10 23:45:54 +02:00
img . style . filter = 'none'
img . style . backgroundSize = 'none'
img . style . backgroundImage = 'none'
2021-07-01 20:51:20 +02:00
}
2021-07-07 19:51:16 +02:00
loadedImageURLs . add ( src )
2021-07-01 20:51:20 +02:00
if ( onLoadingComplete ) {
2021-08-03 01:14:38 +02:00
const { naturalWidth , naturalHeight } = img
// Pass back read-only primitive values but not the
// underlying DOM element because it could be misused.
onLoadingComplete ( { naturalWidth , naturalHeight } )
2021-07-01 20:51:20 +02:00
}
2021-08-18 04:05:10 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
2021-08-26 02:09:04 +02:00
if ( img . parentElement ? . parentElement ) {
const parent = getComputedStyle ( img . parentElement . parentElement )
if ( layout === 'responsive' && parent . display === 'flex' ) {
console . warn (
` Image with src " ${ src } " may not render properly as a child of a flex container. Consider wrapping the image with a div to configure the width. `
)
} else if ( layout === 'fill' && parent . position !== 'relative' ) {
console . warn (
` Image with src " ${ src } " may not render properly with a parent using position:" ${ parent . position } ". Consider changing the parent style to position:"relative" with a width and height. `
)
}
2021-08-18 04:05:10 +02:00
}
}
2021-07-01 20:51:20 +02:00
} )
2021-04-30 19:05:03 +02:00
}
}
2021-07-01 20:51:20 +02:00
if ( img . complete ) {
// If the real image fails to load, this will still remove the placeholder.
// This is the desired behavior for now, and will be revisited when error
// handling is worked on for the image component itself.
handleLoad ( )
} else {
img . onload = handleLoad
}
2021-04-30 19:05:03 +02:00
}
2020-10-14 11:57:10 +02:00
export default function Image ( {
src ,
sizes ,
2020-10-16 22:04:39 +02:00
unoptimized = false ,
priority = false ,
2020-10-22 20:59:42 +02:00
loading ,
2021-07-23 16:44:38 +02:00
lazyBoundary = '200px' ,
2020-10-17 20:55:29 +02:00
className ,
2020-10-20 16:28:01 +02:00
quality ,
2020-10-21 12:03:31 +02:00
width ,
height ,
2020-11-05 20:42:55 +01:00
objectFit ,
objectPosition ,
2021-07-01 20:51:20 +02:00
onLoadingComplete ,
2021-01-05 21:51:34 +01:00
loader = defaultImageLoader ,
2021-04-30 19:05:03 +02:00
placeholder = 'empty' ,
blurDataURL ,
2020-10-31 23:34:06 +01:00
. . . all
2020-10-14 11:57:10 +02:00
} : ImageProps ) {
2020-10-31 23:34:06 +01:00
let rest : Partial < ImageProps > = all
let layout : NonNullable < LayoutValue > = sizes ? 'responsive' : 'intrinsic'
2021-06-02 17:11:03 +02:00
if ( 'layout' in rest ) {
2020-10-31 23:34:06 +01:00
// Override default layout if the user specified one:
if ( rest . layout ) layout = rest . layout
// Remove property so it's not spread into image:
delete rest [ 'layout' ]
}
2021-06-04 10:06:00 +02:00
let staticSrc = ''
if ( isStaticImport ( src ) ) {
const staticImageData = isStaticRequire ( src ) ? src.default : src
if ( ! staticImageData . src ) {
throw new Error (
` An object should only be passed to the image component src parameter if it comes from a static image import. It must include src. Received ${ JSON . stringify (
staticImageData
) } `
)
}
2021-06-10 20:51:35 +02:00
blurDataURL = blurDataURL || staticImageData . blurDataURL
2021-06-04 10:06:00 +02:00
staticSrc = staticImageData . src
if ( ! layout || layout !== 'fill' ) {
height = height || staticImageData . height
width = width || staticImageData . width
if ( ! staticImageData . height || ! staticImageData . width ) {
throw new Error (
` An object should only be passed to the image component src parameter if it comes from a static image import. It must include height and width. Received ${ JSON . stringify (
staticImageData
) } `
)
}
}
}
2021-06-09 00:05:02 +02:00
src = typeof src === 'string' ? src : staticSrc
2021-04-30 19:05:03 +02:00
2021-06-10 20:51:35 +02:00
const widthInt = getInt ( width )
const heightInt = getInt ( height )
const qualityInt = getInt ( quality )
2021-07-08 21:35:19 +02:00
let isLazy =
! priority && ( loading === 'lazy' || typeof loading === 'undefined' )
2021-08-14 00:03:20 +02:00
if ( src . startsWith ( 'data:' ) || src . startsWith ( 'blob:' ) ) {
2021-07-08 21:35:19 +02:00
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
unoptimized = true
isLazy = false
}
if ( typeof window !== 'undefined' && loadedImageURLs . has ( src ) ) {
isLazy = false
}
2020-10-22 20:59:42 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
2020-10-26 15:29:52 +01:00
if ( ! src ) {
throw new Error (
` Image is missing required "src" property. Make sure you pass "src" in props to the \` next/image \` component. Received: ${ JSON . stringify (
2020-10-31 23:34:06 +01:00
{ width , height , quality }
2020-10-26 15:29:52 +01:00
) } `
)
}
2020-10-30 15:33:34 +01:00
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 ( ',' ) } . `
)
}
2021-06-17 22:17:31 +02:00
if (
( typeof widthInt !== 'undefined' && isNaN ( widthInt ) ) ||
( typeof heightInt !== 'undefined' && isNaN ( heightInt ) )
) {
throw new Error (
` Image with src " ${ src } " has invalid "width" or "height" property. These should be numeric values. `
)
}
2021-07-07 21:15:31 +02:00
if ( layout === 'fill' && ( width || height ) ) {
console . warn (
` Image with src " ${ src } " and "layout='fill'" has unused properties assigned. Please remove "width" and "height". `
)
}
2020-10-22 20:59:42 +02:00
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 (
String
) . join ( ',' ) } . `
)
}
if ( priority && loading === 'lazy' ) {
2020-10-22 16:55:51 +02:00
throw new Error (
2020-10-31 23:34:06 +01:00
` Image with src " ${ src } " has both "priority" and "loading='lazy'" properties. Only one should be used. `
2020-10-17 20:55:29 +02:00
)
}
2021-06-10 20:51:35 +02:00
if ( placeholder === 'blur' ) {
2021-06-17 22:17:31 +02:00
if ( layout !== 'fill' && ( widthInt || 0 ) * ( heightInt || 0 ) < 1600 ) {
2021-06-10 20:51:35 +02:00
console . warn (
` Image with src " ${ src } " is smaller than 40x40. Consider removing the "placeholder='blur'" property to improve performance. `
)
}
if ( ! blurDataURL ) {
const VALID_BLUR_EXT = [ 'jpeg' , 'png' , 'webp' ] // should match next-image-loader
throw new Error (
` Image with src " ${ src } " has "placeholder='blur'" property but is missing the "blurDataURL" property.
Possible solutions :
- Add a "blurDataURL" property , the contents should be a small Data URL to represent the image
- Change the "src" property to a static import with one of the supported file types : $ { VALID_BLUR_EXT . join (
','
) }
- Remove the "placeholder" property , effectively no blur effect
Read more : https : //nextjs.org/docs/messages/placeholder-blur-data-url`
)
}
}
2021-07-01 20:51:20 +02:00
if ( 'ref' in rest ) {
console . warn (
` Image with src " ${ src } " is using unsupported "ref" property. Consider using the "onLoadingComplete" property instead. `
)
2021-07-24 00:53:27 +02:00
}
if ( 'style' in rest ) {
console . warn (
` Image with src " ${ src } " is using unsupported "style" property. Please use the "className" property instead. `
)
2021-07-01 20:51:20 +02:00
}
2021-07-08 21:35:19 +02:00
const rand = Math . floor ( Math . random ( ) * 1000 ) + 100
if (
! unoptimized &&
! loader ( { src , width : rand , quality : 75 } ) . includes ( rand . toString ( ) )
) {
console . warn (
` Image with src " ${ src } " has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead. ` +
` \ nRead more: https://nextjs.org/docs/messages/next-image-missing-loader-width `
)
}
2021-07-07 19:51:16 +02:00
}
2020-11-04 22:14:55 +01:00
2020-11-07 00:03:15 +01:00
const [ setRef , isIntersected ] = useIntersection < HTMLImageElement > ( {
2021-07-23 16:44:38 +02:00
rootMargin : lazyBoundary ,
2020-11-07 00:03:15 +01:00
disabled : ! isLazy ,
} )
const isVisible = ! isLazy || isIntersected
2020-10-17 20:55:29 +02:00
2020-10-31 23:34:06 +01:00
let wrapperStyle : JSX.IntrinsicElements [ 'div' ] [ 'style' ] | undefined
let sizerStyle : JSX.IntrinsicElements [ 'div' ] [ 'style' ] | undefined
2020-10-30 15:33:34 +01:00
let sizerSvg : string | undefined
2020-11-05 20:42:55 +01:00
let imgStyle : ImgElementStyle | undefined = {
2020-10-31 23:34:06 +01:00
position : 'absolute' ,
top : 0 ,
left : 0 ,
bottom : 0 ,
right : 0 ,
boxSizing : 'border-box' ,
padding : 0 ,
border : 'none' ,
margin : 'auto' ,
display : 'block' ,
width : 0 ,
height : 0 ,
minWidth : '100%' ,
maxWidth : '100%' ,
minHeight : '100%' ,
maxHeight : '100%' ,
2020-11-05 20:42:55 +01:00
objectFit ,
objectPosition ,
2021-07-20 03:23:16 +02:00
}
const blurStyle =
placeholder === 'blur'
2021-04-30 19:05:03 +02:00
? {
2021-06-09 20:28:44 +02:00
filter : 'blur(20px)' ,
2021-06-30 23:58:26 +02:00
backgroundSize : objectFit || 'cover' ,
2021-04-30 19:05:03 +02:00
backgroundImage : ` url(" ${ blurDataURL } ") ` ,
2021-06-30 23:58:26 +02:00
backgroundPosition : objectPosition || '0% 0%' ,
2021-04-30 19:05:03 +02:00
}
2021-07-20 03:23:16 +02:00
: { }
2021-07-07 21:15:31 +02:00
if ( layout === 'fill' ) {
// <Image src="i.png" layout="fill" />
wrapperStyle = {
display : 'block' ,
overflow : 'hidden' ,
position : 'absolute' ,
top : 0 ,
left : 0 ,
bottom : 0 ,
right : 0 ,
boxSizing : 'border-box' ,
margin : 0 ,
}
} else if (
2020-10-26 15:29:52 +01:00
typeof widthInt !== 'undefined' &&
2021-07-07 21:15:31 +02:00
typeof heightInt !== 'undefined'
2020-10-24 04:01:15 +02:00
) {
// <Image src="i.png" width="100" height="100" />
2020-10-26 15:29:52 +01:00
const quotient = heightInt / widthInt
2020-10-30 15:33:34 +01:00
const paddingTop = isNaN ( quotient ) ? '100%' : ` ${ quotient * 100 } % `
if ( layout === 'responsive' ) {
// <Image src="i.png" width="100" height="100" layout="responsive" />
2020-10-31 23:34:06 +01:00
wrapperStyle = {
display : 'block' ,
overflow : 'hidden' ,
position : 'relative' ,
boxSizing : 'border-box' ,
margin : 0 ,
}
sizerStyle = { display : 'block' , boxSizing : 'border-box' , paddingTop }
2020-10-30 15:33:34 +01:00
} else if ( layout === 'intrinsic' ) {
// <Image src="i.png" width="100" height="100" layout="intrinsic" />
wrapperStyle = {
display : 'inline-block' ,
maxWidth : '100%' ,
2020-10-31 23:34:06 +01:00
overflow : 'hidden' ,
position : 'relative' ,
boxSizing : 'border-box' ,
margin : 0 ,
2020-10-30 15:33:34 +01:00
}
sizerStyle = {
2020-10-31 23:34:06 +01:00
boxSizing : 'border-box' ,
display : 'block' ,
2020-10-30 15:33:34 +01:00
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 = {
2020-10-31 23:34:06 +01:00
overflow : 'hidden' ,
boxSizing : 'border-box' ,
2020-10-30 15:33:34 +01:00
display : 'inline-block' ,
position : 'relative' ,
width : widthInt ,
height : heightInt ,
}
2020-10-21 12:03:31 +02:00
}
} else {
2020-10-22 16:55:51 +02:00
// <Image src="i.png" />
2020-10-21 12:03:31 +02:00
if ( process . env . NODE_ENV !== 'production' ) {
2020-10-22 16:55:51 +02:00
throw new Error (
2020-10-31 23:34:06 +01:00
` Image with src " ${ src } " must use "width" and "height" properties or "layout='fill'" property. `
2020-10-21 12:03:31 +02:00
)
}
}
2020-10-20 18:43:24 +02:00
2020-11-13 05:30:41 +01:00
let imgAttributes : GenImgAttrsResult = {
2021-09-26 17:03:07 +02:00
src : emptyDataURL ,
2020-12-31 22:08:57 +01:00
srcSet : undefined ,
sizes : undefined ,
2020-11-07 18:39:14 +01:00
}
2020-11-07 00:03:15 +01:00
if ( isVisible ) {
2020-11-13 05:30:41 +01:00
imgAttributes = generateImgAttrs ( {
src ,
unoptimized ,
layout ,
width : widthInt ,
quality : qualityInt ,
sizes ,
2021-01-05 21:51:34 +01:00
loader ,
2020-11-13 05:30:41 +01:00
} )
2020-10-26 15:29:52 +01:00
}
2021-07-07 19:51:16 +02:00
let srcString : string = src
2020-10-14 11:57:10 +02:00
return (
2020-10-22 16:55:51 +02:00
< div style = { wrapperStyle } >
2020-10-30 15:33:34 +01:00
{ sizerStyle ? (
< div style = { sizerStyle } >
{ sizerSvg ? (
< img
2020-12-29 18:34:11 +01:00
style = { {
maxWidth : '100%' ,
display : 'block' ,
margin : 0 ,
border : 'none' ,
padding : 0 ,
} }
2020-10-30 15:33:34 +01:00
alt = ""
aria - hidden = { true }
2020-11-07 18:39:14 +01:00
src = { ` data:image/svg+xml;base64, ${ toBase64 ( sizerSvg ) } ` }
2020-10-30 15:33:34 +01:00
/ >
) : null }
< / div >
) : null }
< img
{ . . . rest }
{ . . . imgAttributes }
decoding = "async"
2021-08-20 14:43:44 +02:00
data - nimg = { layout }
2020-10-30 15:33:34 +01:00
className = { className }
2021-07-01 20:51:20 +02:00
ref = { ( img ) = > {
setRef ( img )
2021-08-18 04:05:10 +02:00
handleLoading ( img , srcString , layout , placeholder , onLoadingComplete )
2021-04-30 19:05:03 +02:00
} }
2021-07-20 03:23:16 +02:00
style = { { . . . imgStyle , . . . blurStyle } }
2020-10-30 15:33:34 +01:00
/ >
2021-08-19 06:26:07 +02:00
< noscript >
< img
{ . . . rest }
{ . . . generateImgAttrs ( {
src ,
unoptimized ,
layout ,
width : widthInt ,
quality : qualityInt ,
sizes ,
loader ,
} ) }
decoding = "async"
2021-08-20 14:43:44 +02:00
data - nimg = { layout }
2021-08-19 06:26:07 +02:00
style = { imgStyle }
className = { className }
2021-08-31 21:03:08 +02:00
// @ts-ignore - TODO: upgrade to `@types/react@17`
2021-08-19 06:26:07 +02:00
loading = { loading || 'lazy' }
/ >
< / noscript >
2020-12-30 22:10:28 +01:00
{ priority ? (
// Note how we omit the `href` attribute, as it would only be relevant
// for browsers that do not support `imagesrcset`, and in those cases
// it would likely cause the incorrect image to be preloaded.
//
// https://html.spec.whatwg.org/multipage/semantics.html#attr-link-imagesrcset
< Head >
< link
key = {
'__nimg-' +
imgAttributes . src +
imgAttributes . srcSet +
imgAttributes . sizes
}
rel = "preload"
as = "image"
href = { imgAttributes . srcSet ? undefined : imgAttributes . src }
2021-08-11 02:58:15 +02:00
// @ts-ignore: imagesrcset is not yet in the link element type.
2020-12-30 22:10:28 +01:00
imagesrcset = { imgAttributes . srcSet }
2021-08-11 02:58:15 +02:00
// @ts-ignore: imagesizes is not yet in the link element type.
2020-12-30 22:10:28 +01:00
imagesizes = { imgAttributes . sizes }
> < / link >
< / Head >
) : null }
2020-10-14 11:57:10 +02:00
< / div >
)
}
2021-01-03 16:59:17 +01:00
function normalizeSrc ( src : string ) : string {
2020-10-20 18:43:24 +02:00
return src [ 0 ] === '/' ? src . slice ( 1 ) : src
}
2021-01-05 21:51:34 +01:00
function imgixLoader ( {
root ,
src ,
width ,
quality ,
} : DefaultImageLoaderProps ) : string {
2021-07-06 21:51:50 +02:00
// Demo: https://static.imgix.net/daisy.png?auto=format&fit=max&w=300
const url = new URL ( ` ${ root } ${ normalizeSrc ( src ) } ` )
const params = url . searchParams
params . set ( 'auto' , params . get ( 'auto' ) || 'format' )
params . set ( 'fit' , params . get ( 'fit' ) || 'max' )
params . set ( 'w' , params . get ( 'w' ) || width . toString ( ) )
2020-10-20 16:28:01 +02:00
if ( quality ) {
2021-07-06 21:51:50 +02:00
params . set ( 'q' , quality . toString ( ) )
2020-10-20 16:28:01 +02:00
}
2020-10-21 21:39:12 +02:00
2021-07-06 21:51:50 +02:00
return url . href
2020-10-14 11:57:10 +02:00
}
2021-01-05 21:51:34 +01:00
function akamaiLoader ( { root , src , width } : DefaultImageLoaderProps ) : string {
2020-10-23 16:23:16 +02:00
return ` ${ root } ${ normalizeSrc ( src ) } ?imwidth= ${ width } `
2020-10-21 21:31:28 +02:00
}
2021-01-05 21:51:34 +01:00
function cloudinaryLoader ( {
root ,
src ,
width ,
quality ,
} : DefaultImageLoaderProps ) : string {
2020-12-01 17:09:11 +01:00
// Demo: https://res.cloudinary.com/demo/image/upload/w_300,c_limit,q_auto/turtles.jpg
const params = [ 'f_auto' , 'c_limit' , 'w_' + width , 'q_' + ( quality || 'auto' ) ]
let paramsString = params . join ( ',' ) + '/'
2020-10-20 18:43:24 +02:00
return ` ${ root } ${ paramsString } ${ normalizeSrc ( src ) } `
2020-10-14 11:57:10 +02:00
}
2021-07-08 21:35:19 +02:00
function customLoader ( { src } : DefaultImageLoaderProps ) : string {
throw new Error (
` Image with src " ${ src } " is missing "loader" prop. ` +
` \ nRead more: https://nextjs.org/docs/messages/next-image-missing-loader `
)
2021-07-07 22:20:16 +02:00
}
2021-01-05 21:51:34 +01:00
function defaultLoader ( {
root ,
src ,
width ,
quality ,
} : DefaultImageLoaderProps ) : string {
2020-10-25 06:22:47 +01:00
if ( process . env . NODE_ENV !== 'production' ) {
const missingValues = [ ]
// these should always be provided but make sure they are
if ( ! src ) missingValues . push ( 'src' )
if ( ! width ) missingValues . push ( 'width' )
if ( missingValues . length > 0 ) {
throw new Error (
` Next Image Optimization requires ${ missingValues . join (
', '
) } to be provided . Make sure you pass them as props to the \ ` next/image \` component. Received: ${ JSON . stringify (
{ src , width , quality }
) } `
)
}
2020-11-04 21:47:49 +01:00
if ( src . startsWith ( '//' ) ) {
throw new Error (
` Failed to parse src " ${ src } " on \` next/image \` , protocol-relative URL (//) must be changed to an absolute URL (http:// or https://) `
)
}
if ( ! src . startsWith ( '/' ) && configDomains ) {
2020-10-25 06:22:47 +01:00
let parsedSrc : URL
try {
parsedSrc = new URL ( src )
} catch ( err ) {
console . error ( err )
throw new Error (
2020-11-04 21:47:49 +01:00
` Failed to parse src " ${ src } " on \` next/image \` , if using relative image it must start with a leading slash "/" or be an absolute URL (http:// or https://) `
2020-10-25 06:22:47 +01:00
)
}
2021-06-23 01:02:01 +02:00
if (
process . env . NODE_ENV !== 'test' &&
! configDomains . includes ( parsedSrc . hostname )
) {
2020-10-25 06:22:47 +01:00
throw new Error (
2020-10-27 22:55:21 +01:00
` Invalid src prop ( ${ src } ) on \` next/image \` , hostname " ${ parsedSrc . hostname } " is not configured under images in your \` next.config.js \` \ n ` +
2021-03-29 10:25:00 +02:00
` See more info: https://nextjs.org/docs/messages/next-image-unconfigured-host `
2020-10-25 06:22:47 +01:00
)
}
}
}
2021-06-09 00:05:02 +02:00
return ` ${ root } ?url= ${ encodeURIComponent ( src ) } &w= ${ width } &q= ${ quality || 75 } `
2020-10-14 11:57:10 +02:00
}