'How to query for image height and width in Gatsby.js?

I want to conditionally render sections of a page, depending on the height and width of the images that go in each section.

I source the images from the Contentful CMS. Querying for the images themselves is working. I do want to check if they are horizontal or vertical before I designate them into a section.

To clarify the idea, check attached picture:

enter image description here

I want to check if the height is equal or smaller than the width, and if yes, then it the image goes into the top section, if no, then it will go into the section below.

Here is my code so far:

import React, { useState, useEffect } from 'react'
import { graphql, Link } from 'gatsby'
import Layout from '../components/Layout'


export const query = graphql`
{
  artwork: allContentfulArtwork {
    nodes {
      title
      year
      type
      slug
      image {
        url
        width
        height
      }
    }
  }
}
`



function MalereiPage({ data }) {
    const [horizontalImages, setHorizontalImages]=useState([]);
    const [verticalImages, setVerticalImages]=useState([]);

    useEffect(()=> {
           if(typeof window !== 'undefined'){
        data.artwork.nodes.map(artwork => {
            if(artwork.image.width > artwork.image.height){
                setHorizontalImages(horizontalImages.concat(artwork))
                console.log("HORIZONTAL IMAGES: ", horizontalImages)
            } else{
                setVerticalImages(verticalImages.concat(artwork))
                console.log("VERTICAL IMAGES: ", verticalImages)
            }
        })
           }
    }, [data])

    return (
        <Layout>
            <h2>DAS ZEICHNERISCHE WERK</h2>
            <hr />
            <div className="d-flex justify-content-between werke-container flex-wrap">
                {horizontalImages.map(artwork => (
                    <div key={`artwork-${artwork.slug}`} className="mt-3 d-flex flex-column justify-content-between werk" style={{width: "23%"}}>
                        <div className="mb-3">
                            <h3>{artwork.title},{artwork.year}</h3>
                            <h4>{artwork.type}</h4>
                        </div>
                        <img style={{width: "100%"}} src={artwork.image.url}/>
                    </div>
                ))}
            </div>
        </Layout>
    )
}



export default MalereiPage

console.log() output:

enter image description here



Solution 1:[1]

can't access property "height", data.artwork.nodes.image is undefined

nodes is an array of data, that's why you are able to loop through them in data.artwork.nodes.map. That's the reason the console.log doesn't work as-is. You can always access each specific position like:

{console.log(data.artwork.nodes[0].image.height)}

You can also display the information in the loop like:

{data.artwork.nodes.map(artwork => {
   console.log(artwork);
   return <div key={`artwork-${artwork.slug}`} className="mt-3 d-flex flex-column justify-content-between werk" style={{width: "23%"}}>
        <div className="mb-3">
            <h3>{artwork.title},{artwork.year}</h3>
            <h4>{artwork.type}</h4>
        </div>
        <img style={{width: "100%"}} src={artwork.image.url}/>
    </div>
})}

I think your best option is to populate an array that will contain the top and bottom images according to their width:

export const query = graphql`
{
  artwork: allContentfulArtwork {
    nodes {
      title
      year
      type
      slug
      image {
        url
        width
        height
      }
    }
  }
}
`

const [topImages, setTopImages]=useState([]);
const [bottomImages, setBottomImages]=useState([]);


useEffect(()=>{
   if(typeof window !== 'undefined'){
     data.artwork.nodes.map(artwork=>{
       if(artwork.image.width > window.innerWidth){
           setTopImages(topImages=>[...topImages, artwork])
       } else{
           setBottomImages(bottomImages => [...bottomImages, artwork])
         }
     })
   }
}, [data])

const MalereiPage = ({ data }) => {
    {console.log(data.artwork.nodes.image.height)}
    return (
        <Layout>
            <h2>DAS ZEICHNERISCHE WERK</h2>
            <hr />
            <div className="d-flex justify-content-between werke-container flex-wrap">
                {topImages.map(artwork=> (
                    <div key={`artwork-${artwork.slug}`} className="mt-3 d-flex flex-column justify-content-between werk" style={{width: "23%"}}>
                        <div className="mb-3">
                            <h3>{artwork.title},{artwork.year}</h3>
                            <h4>{artwork.type}</h4>
                        </div>
                        <img style={{width: "100%"}} src={artwork.image.url}/>
                    </div>
                ))}

                 {bottomImages.map(artwork=> (
                    <div key={`artwork-${artwork.slug}`} className="mt-3 d-flex flex-column justify-content-between werk" style={{width: "23%"}}>
                        <div className="mb-3">
                            <h3>{artwork.title},{artwork.year}</h3>
                            <h4>{artwork.type}</h4>
                        </div>
                        <img style={{width: "100%"}} src={artwork.image.url}/>
                    </div>
                ))}
            </div>
        </Layout>
    )
}



export default MalereiPage

There are a few things to comment on in the snippet above, tweak it as you wish of course. The idea relies on populating an array of topImages and bottomImages depending on some calculations/logic. Then, loop through those state-scoped variables to display the images and the artwork.

First of all, use static variables instead of state if you don't need to rehydrate the images on-demand, the approach is exactly the same.

The logic is wrapped in a useEffect hook with data as a dependency to rehydrate the state on-demand and also wrapped in the typeof window !== 'undefined' to avoid SSR (Server-Side Rendering) issues when dealing with global objects like window or document.


Outside the scope of the question: I'd recommend using GatsbyImage rather than img tag to take advantage of all the performance it includes (lazy-loading, srcSet, blur loading, minification, etc). You can read more about the implementation with Contenful at https://www.gatsbyjs.com/plugins/gatsby-source-contentful/#query-for-assets-in-contenttype-nodes

Your query will become something like:

export const query = graphql`
{
  artwork: allContentfulArtwork {
    nodes {
      title
      year
      type
      slug
      imageNodeNameInContentful{
        childImageSharp{
          gatsbyImageData(layout: CONSTRAINED)
        }
      }
    }
  }
}

Then, instead of img you will need to use GatsbyImage with something like:

   {topImages.map(artwork=> {
        const image = getImage(artwork.imageNodeNameInContentful)
        return <div key={`artwork-${artwork.slug}`} className="mt-3 d-flex flex-column justify-content-between werk" style={{width: "23%"}}>
            <div className="mb-3">
                <h3>{artwork.title},{artwork.year}</h3>
                <h4>{artwork.type}</h4>
            </div>
            <GatsbyImage image={image} alt={artwork.title} />
        </div>
    })}

Solution 2:[2]

Using Gatsby image you can get the original height and width from childImageSharp in the GraphQl query.

query MyQuery {
  allFile(filter: {sourceInstanceName: {eq: "images"}, ext: {eq: 
".jpg"}}) {
    nodes {
      id
      name
      relativeDirectory
      childImageSharp {
        original {
          height
          width
        }
        gatsbyImageData(layout: CONSTRAINED, quality: 100)
      }
    }
  }
}

You also used to be able to get aspectRatio from Fluid, but that's now deprecated if you've upgraded to the gatsby-image-plugin.

Whether you can actually get the images to be constrained in their containers according to the aspect ratio is another story. I haven't succeeded.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 mundiverse