thumbnail

なにこれ

こんな感じでgatsby-imageの画像を表示したい。そんなコンポーネントの作成方法です。

  const filename = 'filepath/to/myimage.png'
  return <Image filename={filename} />

gatsby-imageをちょっと知っている人ならわかると思うのですが、gatsby-imageで画像を表示するには、いちいちStaticQueryタグとGraphQLのクエリを定義する必要があって結構面倒です。そして公式ドキュメントには画像のファイルパスをGraphQLのクエリにハードコードする方法しか載っておらず、Reactコンポーネントで画像ファイルパスを変数として扱う方法については記載がありません。 そこで今回は、画像ファイルパスを渡せばgatsby-imageで画像を最適化して表示してくれるコンポーネントを作りました。

きっかけは、最近自分のブログの記事一覧のデザインを変えて、サムネイル画像を表示するようにしました。今までは体感速度がそれほど遅くないし、gatsby-imageは使い方が面倒くさそうだったので避けていました。たださすがに、50個以上のサムネイル画像を表示するようになると明らかに遅くなったので、gatsby-imageの導入を決めました。

実際のソースコードは以下です。手っ取り早く実装方法を見たい方はコチラをご覧ください。

https://github.com/Takumon/blog/blob/master/src/components/image/index.js

実装

うまくいかない例

公式ドキュメント > How to useではGraphQLのクエリに画像ファイルパスをハードコードする方法が紹介されています。 コレを参考にして、画像ファイルパスを変数として扱うには以下のようにすれば良さそうな気がします。

うまくいかない例
import React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import Img from 'gatsby-image'


// 画像ファイルパスをプロパティに取るようなコンポーネントを定義
export default ({ filename }) => (

  // ページじゃないコンポーネントでもGraphQLが使えるように
  // StaticQueryタグを使う
  <StaticQuery

    // GraphQLのクエリ引数にfilenameを指定!
    query={graphql`
      query {
        file(relativePath: { eq: "${filename}" }) {
          childImageSharp {
            fixed(width: 800) {
              ...GatsbyImageSharpFixed
            }
          }
        }
      }
    `}

    render={(data) => {
      // GraphQLでgatsby-image用の情報がdataに代入されているはず!
      const fixed = data.file.childImageSharp.fixed

      // Imgタグでgatsby-imageで最適化された画像を表示する
      return <Img fixed={fixed} />
    }
  />
)

ただこれはうまく行きません。 StaticQueryタグは文字通り「静的」であり、ビルド時に評価されるので、画面表示時に「動的」に変数を指定することはできないのです。またStaticQueryのクエリはテンプレートリテラルとして文字列補完をサポートしておらず、その結果ビルド時に以下のようなエラーになってしまいます。

Error: BabelPluginRemoveGraphQL: String interpolations are not allowed in graphql fragments.
Included fragments should be referenced as ...

うまくいく例

前述のうまくいかない例では、GraphQLのクエリで画像ファイルを特定しようとしていました。 今回はコレをGraphQLのクエリですべての画像を取得して、描画時に画像ファイルを特定するようにします。

うまくいく例
import React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import Img from 'gatsby-image'

// 画像ファイルパスをプロパティに取るようなコンポーネントを定義
export default ({ filename }) => (

  // ページじゃないコンポーネントでもGraphQLが使えるように
  // StaticQueryタグを使う
  <StaticQuery

    // GraphQLのクエリ引数には何も指定しない!
    query={graphql`
      query {
        images: allFile {
          edges {
            node {
              relativePath
              name
              childImageSharp {
                sizes(maxWidth: 800) {
                  ...GatsbyImageSharpSizes
                }
              }
            }
          }
        }
      }
    `}

    // 全画像情報がdataに代入されている
    render={(data) => {

      // 指定した画像ファイルパス(コンポーネントのプロパティ)と
      // 一致するgatsby-image用の情報を取得
      const image = data.images.edges.find(n => {
        return n.node.relativePath.includes(filename)
      })

      if (!image) return
      
      // Imgタグでgatsby-imageで最適化された画像を表示する
      const imageSizes = image.node.childImageSharp.sizes
      return <Img sizes={imageSizes} />
    }}
  />
)

これによってめんどくさい処理をコンポーネントに隠蔽できました。使う側は以下のように定義します。

コンポーネントの使用イメージ
import Image from '上記で作成したコンポーネントのパス'

export default ({ filename }) => {
  const filename = 'filepath/to/myimage.png'
  return <Image filename={filename} />
})

まとめ

今回はgatsby-imageを楽に使えるコンポーネントの作成方法を紹介しました。GraphQLのクエリですべての画像を取得するので、ビルドがちょっと遅くなります(実際のブラウザの表示には影響はありません)。それでも画像のファイルパスを変数として扱うことができるようになるし、実装が簡潔になるので、利便性は高いと言えるでしょう🍅

参考

実装は以下を参考にさせていただきました。ありがとうございます。