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