具有动态图像源的可重用 Gatsby-Image 组件

IT技术 javascript reactjs graphql gatsby
2021-04-07 18:09:51

我正在考虑将 Gatsby-Image 用于我的下一个项目,并且一直在尝试使用它。

我让它在我的测试项目中工作,但后来我想出了一个用例,我想像使用常规<img src”image.png”>标签一样使用来自 Gatsby 的标签。因此,我的问题是如何使 Gatsby 组件可重用?

import React from "react"
import { StaticQuery, graphql } from "gatsby"
import Img from "gatsby-image"
function renderImage({ file }) {
  console.log({ file })
  return <Img fluid={file.childImageSharp.fluid} />
}

// Stateless Image component which i guess will recieve src value as a prop?
// Returns a StaticQuery component with query prop and render prop. Query prop has the graphql query to recieve the images and render prop returns a renderImage function which in return, returns a Img component från Gatsby with set attributes.
const Image = () => (
  <StaticQuery
    query={graphql`
      query {
        file(relativePath: { eq: "gatsby-astronaut.png" }) {
          childImageSharp {
            fluid(maxWidth: 300) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    `}
    // render={data => <Img fluid={data.placeholderImage.childImageSharp.fluid} />}
    render={renderImage}
  />
)
export default Image

我的最佳用例是向我的 Gatsby.config 文件中定义的 relativePath 发出动态请求,然后组合每个 Gatsby 中的 src props,并将其与我的资产文件中的所有图像匹配,然后显示它。你们中有人知道这是否可行吗?

我在文档中读到静态查询不能接受变量 - 只有页面。但我不希望我的图像与页面相关联——我想在任何我想要的地方使用这个组件——就像一个常规的 img 标签。

希望我已经说清楚了。如果您有任何问题,请提问。

这是一个例子:https : //codesandbox.io/s/py5n24wk27

预先感谢,埃里克

4个回答

我也一直在寻找这个答案。希望这能回答你的问题:

最终代码:

import React from 'react';
import { StaticQuery, graphql } from 'gatsby';
import Img from 'gatsby-image';

// Note: You can change "images" to whatever you'd like.

const Image = props => (
  <StaticQuery
    query={graphql`
      query {
        images: allFile {
          edges {
            node {
              relativePath
              name
              childImageSharp {
                fluid(maxWidth: 600) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        }
      }
    `}
    render={data => {
      const image = data.images.edges.find(n => {
        return n.node.relativePath.includes(props.filename);
      });
      if (!image) {
        return null;
      }

      //const imageSizes = image.node.childImageSharp.sizes; sizes={imageSizes}
      return <Img alt={props.alt} fluid={image.node.childImageSharp.fluid} />;
    }}
  />
);

export default Image;

使用图像:

import Image from '../components/Image';
<div style={{ maxWidth: `300px` }}>
    <Image alt="Gatsby in Space" filename="gatsby-astronaut.png" />
</div>

解释

因为 StaticQuery 在其模板文字中不支持字符串插值,所以我们无法真正向它传递任何props。相反,我们将尝试处理 StaticQuery 的 Render 部分中的props检查。

注意事项

我不是 100% 确定这是否会影响编译时间,因为我们正在扫描所有图像。如果有,请告诉我!

更新:如果您有很多图像,捆绑包的大小可能会变得非常大,因为此解决方案确实会扫描所有图像。

进一步定制

如果没有传递props,您可以调整代码以显示占位符图像。

备择方案

也就是说,还有另一种方法可以解决这个问题,但需要更多的工作/代码。

来源

  • 我修改了这篇文章中的代码(请注意,该文章使用的是已弃用的代码。)
我将allFile 与relativeDirectory 一起使用。所以我可以只渲染特定子文件夹中的图像。allFile(filter: {relativeDirectory: {eq: "team"}}) {...}
2021-05-29 18:09:51
这很好。请注意,您需要在包含所有这些图像的 javascript 包中支付费用。如果都用了就没问题。但是,如果您添加 2-3 个图像组件以获得不同尺寸的渲染(不同选项),那么您将支付捆绑包中的所有费用。-- 因此,如果您可以为所有图像保留相同的选项并且您使用 graphql 查询中的几乎所有图像,这将很有效。
2021-06-01 18:09:51
盖茨比队真的错过了这个球。谁会想到 Web 开发人员不想为每张图像都进行单独的查询和组件。/秒
2021-06-06 18:09:51
fluid 现在也已弃用。
2021-06-07 18:09:51

不幸的是,据我所知,最好的解决方案是为图像写出单独的 js 文件。

在@RodrigoLeon 的方法中,它会导致包大小显着增加。特别是如果说你有超过 50 张图片。因为无论何时您使用它并遍历所有图像,您都会在组件文件中创建对它们的引用。所以我不建议这样做。

我正在建立的网站是一个电子商务平台,拥有数千张图片(所有产品)。这提出了使用 gatsby 查询图像的主要问题。很长一段时间,我有一个组件可以查询所有图像并将它们与各自的产品相匹配。(就像在this中提出的那样)这是非常低效的,会抛出有关查询持续时间的警告。

另一种方法是在数据级别将 imageFile 附加到产品,而不是在尝试渲染时。

src/gatsby-api/create-resolvers/index.js

const resolvers = {
    AWSAppSync_Product: {
        imageFile: {
            type: 'File',
            resolve: async (source, args, context, info) => {
                const node = await context.nodeModel.runQuery({
                    query: {
                        filter: {
                            Key: { eq: source.image1 }
                        }
                    },
                    type: 'S3Object',
                    firstOnly: true
                });

                if (node && node.imageFile) return node.imageFile;
            }
        },
    },
}

module.exports = {
    resolvers
}

gatsby-node.js

exports.createResolvers = async ({ createResolvers }) => {
    createResolvers(resolvers)
}

源代码/组件/图像/index.js

import React from 'react'
import Img from 'gatsby-image'

export const Image = props => {
  if (props.imageFile && props.imageFile.childImageSharp && props.imageFile.childImageSharp.fluid) {
    return <Img className={props.imgClassName} alt={props.alt} fluid={props.imageFile.childImageSharp.fluid} />;
  }
};

然后像这样使用它:

<Image
  imageFile={product.imageFile}
  alt=""
/>

AWSAppSync_Product是我将文件附加到的节点类型。(可以在本地主机上的 graphql 游乐场中找到)。解析将匹配产品Key上的S3Objectwith image1(这是一个字符串)。这使我可以直接使用产品图像,而无需在图像组件内运行查询。

在我看来,一旦你把它放在心上,这是一条有value的信息,它肯定对我有很大帮助。

是的,这是一种在 gatsby 生态系统中使用的技术,因为在构建之前附加数据总是最有效的(以提高性能)。这显然不适用于前端请求,必须在构建时知道图像和数据。你能解释一下为什么这不是问题的解决方案吗?在构建时不知道图像数据的情况很少见。通过提前请求数据,您可以避免使用获取数据的组件。
2021-05-25 18:09:51
啊是的,这完全绕过了前端组件解决方案,并且不支持客户端对数据的调用;在构建时需要所有数据。感谢您的澄清。
2021-05-29 18:09:51
很好,我们同意第一部分。但是,我并不是说您的解决方案不能解决问题。我说过,当您要处理“真正的动态数据”时,这不是解决方案。因此,如果您很严格并且想要<Img>“像常规 img 标签一样”使用 Gatsby 的组件(如问题所述),这将不起作用。对于不需要显示真实动态图像的人来说,这仍然不错,但是一旦有人实现了例如“加载更多产品”AJAX 调用,涉及使用 Gatsby Image 显示添加产品的图像,它就无法工作。
2021-06-01 18:09:51
你基本上做了Gatsby 的尖锐插件已经做的事情,但不是在文件类型上获取图像,而是在你自己的类型上获取它。这是一个应该考虑的合法解决方案,如果数据在编译时是已知的(在您的情况下,Gatsby 知道您请求什么产品,因此它只会准备所有使用的图像)。如果您想将 Gatsby Image 用于真正的动态数据,这绝不是一个解决方案。你的回答是一个很好的提醒,但没有解决问题的方法。
2021-06-06 18:09:51

如果您将 Wordpress 与 WP GraphQL 一起使用,并想动态加载一些帖子,您将面临同样的问题。您将无法使用出色的预处理功能,例如降低质量和使用粗略的 base64 占位符。正如前面提到的,@RodrigoLeon 解决方案是可行的,但如果您的网站最终在图像中增长,您将面临巨大的负载。

由于我的网站将包含大量帖子,并且会动态加载此类帖子,因此我必须想出一个至少可以接受的解决方案。我最终要做的是为我的网站的动态部分生成 childImageSharp(并提供一个通用的 base64 占位符),因此我总是可以传递childImageSharp<Img>Gatsby组件。

以下是帖子中特色图像的流体图像类型示例:

  • 确保将其包含在您的 GQL 中:
featuredImage {
  node {
    sourceUrl
    mediaDetails {
      file
      width
      height
      sizes {
        file
        name
        width
        sourceUrl
      }
    }
  }
}

加载您的帖子后,通过此功能发送文件节点(featuredImage)的每个节点:

/**
 * Attaches a sharped image to the node for Gatsby Image.
 * @param image Dynamic image node to expand.
 * @param maxWidth Real existing width of file to use as default size.
 */
function attachChildImageSharp(
  image,
  maxWidth
) {
  const mediaDetails: IWpMediaDetails = image.mediaDetails;
  if (mediaDetails) {
    maxWidth = maxWidth || mediaDetails.width;
    image.localFile = image.localFile || {};
    image.localFile.childImageSharp = image.localFile.childImageSharp || {};
    const childImageSharp = image.localFile.childImageSharp;

    // only supporting fluid right now:
    const fluid = (childImageSharp.fluid =
      childImageSharp.fluid || {});
    fluid.aspectRatio =
      mediaDetails.width && mediaDetails.height
        ? mediaDetails.width / mediaDetails.height
        : undefined;
    fluid.originalImg = image.sourceUrl;
    fluid.originalName = mediaDetails.file;
    fluid.presentationHeight =
      fluid.aspectRatio && maxWidth
        ? Math.round(maxWidth / fluid.aspectRatio)
        : mediaDetails.height;
    fluid.presentationWidth = maxWidth;
    fluid.sizes = `(max-width: ${maxWidth}px) 100vw, ${maxWidth}px`;

    const srcSets = [];
    const allowedSizes = ["medium", "medium_large", "large"];
    mediaDetails.sizes.forEach((size) => {
      if (allowedSizes.indexOf(size.name) >= 0) {
        if (
          size.width === `${fluid.presentationWidth}`
        ) {
          fluid.src = size.sourceUrl;
        }
        srcSets.push(`${size.sourceUrl} ${size.width}w`);
      }
    });

    fluid.srcSet = srcSets.join(",\n");
  } else {
    console.warn("Unable to attach dynamic image sharp: Missing mediaDetails.");
  }
}

您将像这样调用该函数(也是附加通用 base64 图像的好地方):

posts.nodes.forEach((post) => {
  attachChildImageSharp(post.featuredImage.node, 768);
   post.featuredImage.node.localFile.childImageSharp.fluid.base64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2NgYGD4DwABBAEAcCBlCwAAAABJRU5ErkJggg=="; // 1x1 black PNG, from https://shoonia.github.io/1x1/#000000ff
});

请注意,不可变对象会导致错误,因此请禁用缓存(fetchPolicy: 'no-cache'如果您使用 ApolloClient)。选择一个宽度作为第二个参数,它是您在 Wordpress 设置中为图像大小(不包括缩略图)选择的三个宽度之一。

此解决方案适用于 Gatsby Image V1,并且通过所有尝试,它并不完美,但它满足了我的需要。