如何在Next.js中使用Image组件处理未知宽度和高度的图片

38
自 10 版本以来,Next.js 自带内置的 Image 组件,可以进行图像优化和响应式调整大小。我非常喜欢它,并且一直在我的网站上使用它来处理固定尺寸的图像。根据官方文档,除非是 layout=fill,否则需要指定宽度和高度。
现在,即使事先不知道图像的宽度和高度,我也想使用 Image 组件。我有来自 CMS 的博客资源,其中图像的确切大小未知。在这种情况下,是否有办法使用 Image 组件呢?
感谢您的任何帮助。
13个回答

27

是的,你可以使用你提到的 layout=fill 选项。

在这种情况下,你需要为你的图片设置一个“纵横比”。

<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>

你也可以使用objectFit="cover",它会扩大你的图像以填充整个容器。

这种方法的缺点是它将依附于你指定的width,它可以是相对的,比如“100%”,也可以是绝对的,比如“10rem”,但没有办法根据图像的大小设置自动宽度和高度,或者至少还没有。


2
我不想事先设置纵横比,可能会扭曲图像。唉,我想现在只能坚持使用 img 了。谢谢你的回答。 - bubbleChaser

16

我也遇到了同样的问题:我想在MUI的砌体ImageList中使用来自next/imageImage...

无论如何,这是我用来获取图片大小及处理容器大小的技巧:

    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;
    `;

这个想法:在图片加载时获取其大小,计算出比例,并用它来使容器填充所需空间,以填充顶部的内边距。这样无论您的图片大小如何,容器都会适应它。

备注:我没有在容器上使用宽度或高度,因为对于我的项目来说不需要,但是对于您的项目可能需要设置其中一个或两个。

由于我仍在学习React & NextJS & typescript,如果有人有改进解决方案的想法,我很高兴能够阅读!


7
我已经在网上搜索了两个小时,这是唯一有效的方法。我无法相信让一张图片宽度自适应其父级元素且高度自动调整这么难。这真是令人发疯。 - JordyJordyJordyJordan
3
这个解决方案可以改进,不要使用onLoad方法,使用onLoadingComplete更有效率,因为你可以直接解构({naturalWidth, naturalHeight}) =>(...),这样就可以在一行代码中设置填充! - Paul Bompard
你是我的救星。我花了大约4个小时来寻找解决这个问题的方法,而这个技巧解决了我的问题。非常感谢。 - 2Up1Down
1
@PaulBompard 很有趣。对于代码整洁方面,我有一个想法:这不是使用“useLayoutEffect”默认的React钩子的完美场景吗?它在浏览器中绘制之前,在所有内容都加载完成后执行,对吧?尽管应该避免使用这个钩子,但在这种情况下,它可能是适当的场景之一,官网上有使用示例:https://beta.reactjs.org/reference/react/useLayoutEffect - faebster
当使用您提供的代码并包含时,Masonry布局不正常工作,图像高度是动态的,但容器高度仍然相同,并且图像之间存在奇怪的空白。当使用cover时,最终每个图像具有相同的高度。您知道如何修复它吗? @JordyJordyJordyJordan - Wokers
当使用您提供的代码时,我的图像(有些是垂直的,有些是水平的)即使具有不同的宽高比,也会获得相同的高度,并且垂直的图像周围还有空白来使其与水平图像具有相同的高度。我该如何使其按照 MUI 砌体文档中的方式运作?(每个图像具有不同的高度) - Wokers

4

如果您想保留原始图像的比例,可以按照以下方式进行操作:

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

图像将填充到容器所允许的大小。例如,如果您知道您的图像比长宽更宽,您可以仅在容器上设置宽度,然后将容器高度设置为比预测的图像高度更大。
因此,例如,如果您正在使用宽图像,则可以将容器宽度设置为150px,然后只要容器高度大于图像高度,图像就会以原来比例的高度显示出来。

3

我写了一篇博客文章,详细介绍了如何在使用SSG的同时获取远程图像的大小。 如何在Next.js中使用Image组件处理未知宽高的图像

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}
        />
      ))}
    </>
  );
}
 

1
虽然这个链接可能回答了问题,但最好在这里包含答案的必要部分并提供参考链接。如果链接页面发生更改,仅链接的答案将变得无效。-【来自审核】 - kwsp

3

没有任何以上变量能帮助我,但我尝试了另一种解决方法,对我有效:

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}
/>

这可能可行,这也是我考虑过的事情...但我会持保留态度...据我所知:NextJS服务器端会将图像缩小为11像素,客户端在11图像加载后请求新图像^^(优化交付得到保证?)这将是一个额外的网络请求,甚至可能使图像无法缓存?除非Next.js理解图像在onLoad事件后增长(但那样他们就不会让我们使用这个“hack”)。 - faebster

0
有时候你想要不同的图片尺寸,比如小、中、大。我们向组件传递一个属性,根据这个属性,我们使用不同的样式。在这种情况下,layout="fill"非常方便。

当填充时,图片将会拉伸宽度和高度以适应父元素的尺寸,前提是该元素是相对定位的

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>
  );
};

你需要为每种情况编写CSS样式。重要的是你应该有position:relative。例如:

.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;
}

0

这是我的解决方案,适合父容器。还要考虑图像是垂直还是水平的情况。宽度是动态设置的。

确保你改变了最大高度!

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

我经常遇到这样的问题,即想要一个图像按比例缩放以填充屏幕宽度。在这种情况下,您知道您需要 width: 100%,并且似乎 height: auto 会自动缩放,但实际上并不起作用。关键是您需要为所使用的图像设置正确的 aspect-ratio。您可以在 CSS 中完成此操作。例如:

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

请注意,如果您为图像设置了错误的aspect-ratio,则它将无法按预期填充宽度或高度。

0
在大多数情况下,您希望在保持纵横比的同时设置自定义宽度属性使用图像。这里是我找到的一个解决方案。您必须在下一个项目中使用静态导入的图像。 在下面的代码中,我使用了tailwindCSS进行样式设置。
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>
  );
}

这将呈现一个宽度为100像素的图像,高度由图像比例确定。相对位置是关键属性。


0
@Oleskii,你给出的答案帮助我想到了以下解决方案:
我的目标是将任何大小的图像适应一个正方形容器中,而不调整图像的感知宽高比。
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,
       });
      }
    }}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接