20

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
  • 1
  • 7
  • 79
  • 120
bubbleChaser
  • 415
  • 1
  • 5
  • 16

6 Answers6

17

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
  • 387
  • 3
  • 5
  • 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
4

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!

1

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,067
  • 15
  • 19
1

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
  • 29,816
  • 26
  • 86
  • 96
Elfandrei
  • 21
  • 3
  • 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
  • 1
  • 7
  • 79
  • 120
-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
  • 23
  • 1
  • 2