35

Since version 10, Next.js comes with the built-in Image component which provides the feature of image optimization and resizing image responsively. I like it a lot and I've been using it across my website for images with a fixed size. According to the official documentation, width and height are required props unless it's for layout=fill.

Now, I would like to use Image component even when I don't know width and height ahead of time. I have blog resources that come from a CMS where the exact size of the image is unknown. In this case, is there any way I could use Image component?

Any help would be appreciated.

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
bubbleChaser
  • 755
  • 1
  • 8
  • 21

12 Answers12

26

Yeah, you could use it with the layout=fill option you mentioned.

On this case, you're gonna need to set an "aspect ratio" for your images

<div style={{ position: "relative", width: "100%", paddingBottom: "20%" }} >
  <Image
    alt="Image Alt"
    src="/image.jpg"
    layout="fill"
    objectFit="contain" // Scale your image down to fit into the container
  />
</div>

You can use objectFit="cover" too and it'll scale your image up to fill all the container.

The downside of this approach is that is gonna be attached to the width you specify, it could be relative such as "100%" or absolute e.g. "10rem", but there's no way to set like an auto width and height based on the size of your image, or at least no yet.

edgarlr
  • 583
  • 5
  • 7
  • 2
    I don't want to set up aspect ratio in advance, possibly distorting the image. Oh well, I guess I have to stick with `img` for now. Thank you for your answer. – bubbleChaser Feb 25 '21 at 04:54
16

I had the same issue too: I wanted to use Image from next/image in a masonry ImageList from MUI...

Anyway, this is the trick I used to get the size of my image and so to handle the size of the container:

    import React, { FC, useState } from 'react';
    import styled from 'styled-components';
    import Image from 'next/image';
    
    interface Props {
      src: string;
    }
    export const MasonryItem: FC<Props> = ({ src }) => {
      const [paddingTop, setPaddingTop] = useState('0');
    
      return (
        <Container style={{ paddingTop }}>
          <Image
            src={src}
            layout="fill"
            objectFit="contain"
            onLoad={({ target }) => {
              const { naturalWidth, naturalHeight } = target as HTMLImageElement;
              setPaddingTop(`calc(100% / (${naturalWidth} / ${naturalHeight})`);
            }}
          />
        </Container>
      );
    };
    
    const Container = styled.div`
      position: relative;
    `;

The idea: get size of image when it's loaded, calc the ratio and use it to make the container fill the require space with padding-top. This way no matter your image size the container will be fit to it.

Remark, I didn't use width or height on the container because I didn't need it with my project, but maybe you will have to set one of both for your project.

As I'm still beginning with React & NextJS & typescript, maybe the solution can be prettier, if someone have an idea to improve it I'll be pleased to read it!

Paul Bompard
  • 170
  • 1
  • 7
  • 7
    Been scouring the internet for seriously two hours now and this is the only thing that remotely works. I can't believe it's this difficult to make an image with a width of 100% of it's parent and an auto height. Bonkers to me. – JordyJordyJordyJordan Jun 27 '22 at 22:17
  • 3
    This solution can be improved, instead of using the onLoad method, it's more efficient to use `onLoadingComplete` because you can directly destructure `({ naturalWidth, naturalHeight }) => (...)` and so, set the padding in 1 unique line ! – Paul Bompard Jul 07 '22 at 13:20
  • You are my saviour. I spent like 4 hours to find a solution to this problem and this trick solved my case. Big thanks. – 2Up1Down Jan 04 '23 at 18:56
  • 1
    @PaulBompard interesting. just one thought towards clean code: Wouldn't this be a perfect scenario for "useLayoutEffect" default react hook? It is executed when everything is loaded but before painting in the browser, right? Even though the hook should be avoided, in this case it might be one of the appropriate scenarios, usage example on official website: https://beta.reactjs.org/reference/react/useLayoutEffect – faebster Mar 01 '23 at 21:17
  • when the code you provided is used with contain, mansory idea doesn't work properly, image heights are dynamic but the container heights are still the same and there are weird spaces between images. when cover is used, every image has the same height in the end. do you know how to fix it? @JordyJordyJordyJordan – Wokers May 11 '23 at 23:21
  • when the code you provided is used, my images (somes are vertical, somes are horizontal) get the same height even though they have different aspect ratios and the vertical ones have spaces around to make the same height as horizontal ones. how can i make it in a way that it works as in mui masonry docs? (different heights for every image) – Wokers May 11 '23 at 23:48
4

If you want to preserve the ratio of the original image you can do it like this:

<div
  style={{
    width: 150,
    height: 75,
    position: "relative",
  }}>
<Image
  src={"/images/logos/" + merger.companyLogoUrl}
  alt={merger.company}
  layout="fill"
  objectFit="contain"
/>

The image will fill to be as big as the container allows. So if you know your images are wider than they are long, for example, you could just set the width on the container and then set the container height to be bigger than the predicted image height.

So, for example, if you're working with wide images you can set the container width to be 150px, then as long as the container height is larger than the image height the image will show up at the height of the original ratio.

Santiago Angel
  • 1,127
  • 15
  • 19
4

nothing wasn't helped me from above variants, but i tried resolve issue another way, it works for me:

const [imageSize, setSmageSize] = useState({
  width: 1,
  height: 1
 });
    
<Image
  src={imgPath}
  layout="responsive"
  objectFit="contain"
  alt={alt}
  priority={true}
  onLoadingComplete={target => {
    setSmageSize({
      width: target.naturalWidth,
      height: target.naturalHeight
    });
   }}
  width={imageSize.width}
  height={imageSize.height}
/>
Oleksii
  • 269
  • 3
  • 4
  • that's could work, also a thing I thought about... but I'm taking this with a grain of salt... as I understand: nextJS serverside would shrink the image to 1*1 pixel and client requests a new one after 1*1 image is loaded ^^ (optimized delivery ensured?) that would be an additional network request and could even make the image uncacheable? unless Next.js understands that image grows after onLoad event (but then they wouldn't let us make this 'hack') – faebster Mar 01 '23 at 21:30
2

I wrote a blog post, detailing how to get the size of remote images, while also using SSG. How to use Image component in Next.js with unknown width and height

import Image from "next/image";
import probe from "probe-image-size";

export const getStaticProps = async () => {
  const images = [
    { url: "https://i.imgur.com/uCxsmmg.png" },
    { url: "https://i.imgur.com/r4IgKkX.jpeg" },
    { url: "https://i.imgur.com/dAyge0Y.jpeg" }
  ];

  const imagesWithSizes = await Promise.all(
    images.map(async (image) => {
      const imageWithSize = image;
      imageWithSize.size = await probe(image.url);

      return imageWithSize;
    })
  );

  return {
    props: {
      images: imagesWithSizes
    }
  };
};
//...
export default function IndexPage({ images }) {
  return (
    <>
      {images?.map((image) => (
        <Image
          key={image.url}
          src={image.url}
          width={image.size.width}
          height={image.size.height}
        />
      ))}
    </>
  );
}
 
Machavity
  • 30,841
  • 27
  • 92
  • 100
Elfandrei
  • 53
  • 7
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/31189548) – kwsp Mar 05 '22 at 01:58
0

Sometimes you want to have different image sizes, like small, medium, and large. We pass a prop to the component and based on this prop, we use different styling. In this case, layout="fill" is handy.

when fill, the image will stretch both width and height to the dimensions of the parent element, provided the element is relative

const Card = (props) => {
  const { imgUrl, size } = props;
  const classMap = {
    large: styles.lgItem,
    medium: styles.mdItem,
    small: styles.smItem,
  };
  return (
      <div className={classMap[size]}>
        <Image src={imgUrl} alt="image" layout="fill" />
      </div>
  );
};

You write css styling for each case. Important thing is you should have position:relative. for example:

.smItem {
  position: relative;
  width: 300px;
  min-width: 300px;
  height: 170px;
  min-height: 170px;
}

.mdItem {
  position: relative;
  width: 158px;
  min-width: 158px;
  height: 280px;
  min-height: 280px;
}

.lgItem {
  position: relative;
  width: 218px;
  min-width: 218px;
  height: 434px;
  min-height: 434px;
}
Yilmaz
  • 35,338
  • 10
  • 157
  • 202
0

In most cases, you want to use an image while keeping the aspect ratio and setting the custom width property. Here is a solution I found. You have to use statically imported images in the next project. In the below code, I used tailwindCSS for styling.

import React from 'react';
import Image from 'next/image';

import mintHeroImg from '../public/images/mint-hero.png';

export default function Lic() {
  return (
    <div className="w-[100px] relative">
      <Image src={mintHeroImg} alt="mint-hero" />
    </div>
  );
}

This will render an image with the width as 100px and the height is determined by the image ratio. The relative position is the essential property.

James Risner
  • 5,451
  • 11
  • 25
  • 47
Xiao Wang
  • 302
  • 2
  • 7
0

@Oleskii the answer you gave helped me to come up with the following:

My goal is to fit any image, regardless of sizing, within a square container without adjusting the perceived aspect ratio of the image

onLoadingComplete={(target: any) => {
   const aspectRatio = target.naturalWidth / target.naturalHeight
      if (target.naturalHeight > target.naturalWidth) {
        setImageSize({
          height: containerWidth / 2,
          width: containerWidth / 2 * aspectRatio
        })
      } else {
        setImageSize({
          width: target.naturalWidth,
          height: target.naturalHeight,
       });
      }
    }}
0

Here is my solution which fits the parent container. Also takes care of whether the image is vertical or horizontal. The width is dynamically set.

Make sure you change your max-height though!

import { StaticImageData } from "next/image";
import Image from "next/image";
import styled from "styled-components";

interface Props {
    image: StaticImageData;
    altText: string;
}

const ImageDisplay = ({ image, altText }: Props) => {
    return (
        <ImageContainer image={image}>
            {image.width > image.height ? (
                <Image src={image} className={"imgHorizontal"} alt={altText} />
            ) : (
                <Image src={image} className={"imgVertical"} alt={altText} />
            )}
        </ImageContainer>
    );
};

const ImageContainer = styled.div<{ image: StaticImageData }>`
    display: flex;
    justify-content: center;

    width: 100%;
    max-height: 1000px;
    ${(props) => props.image.width < props.image.height && `height: ${props.image.height}px;`}

    .imgVertical {
        object-fit: contain;
        width: auto;
        height: 100%;
    }

    .imgHorizontal {
        object-fit: contain;
        width: 100%;
        height: auto;
    }
`;
export default ImageDisplay;
0

I've run into the problem many times where I want an image to scale proportionally to fill the width of the screen. In this case you know you want width: 100% and it seems like height: auto would automatically scale but this doesn't work. The key is that you need to set the proper aspect-ratio for the image you're using. You can do this in CSS. For example:

<div 
  style={{
    position: "relative",
    width: "100%",
    aspect-ratio: 16/9     
  }}
>
  <Image 
    src="/path/to/image.png"          
    layout="fill"          
  />
</div>   

Note that if you set aspect-ratio wrong for the image then it wont fill the width or height as expected.

Justin
  • 300
  • 3
  • 11
0

For Next 12 and tailwind css.

Here is what I have for auto height and width scaled to 100% of the parent div:

<div>
  <Image 
  sizes="(max-width: 100%)" 
  src={srcImageImport} 
  alt="My Image" />
</div>
T. Eric Hong
  • 548
  • 4
  • 12
-2
<Image src={thumbnail || ""}
       alt="post-thumbnail"
       layout="responsive"
       objectFit="cover"
       width={6}
       height={4}
/>

width={6} height={4} is ratio
Ex: square => width={1} height={1}

letrungdo
  • 21
  • 1
  • 2