Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Gatsbyjs add class to gatsby-image based on aspect ratio

gatsby-image wraps each image with a gatsby-image-wrapper div which fills 100% of the available viewport width. This wrapper div can easily be styled with CSS but there is no way to treat landscape, portrait or square images differently from each other.

like this example What if, you wanted to have landscape images fill 80%-100% of the available width but have portrait and square images fill no more than 40-50% of the viewport width.

So ideally each gatsby-image-wrapper div gets a class added depending on its aspect ratio, which would be either; landscape, portrait or square.

One way to do this could be to write some conditional statement using the aspect ratio that comes with childImageSharp:

      edges {
        node {
          name
          childImageSharp {
            fluid(maxWidth: 915, quality: 90) {
              aspectRatio
              ...GatsbyImageSharpFluid_withWebp
            }
          }
        }
      }

When I map over all my gallery images, I can grab the aspect ratio and add it to each gatsby-image-wrapper using className but it's not very useful in its raw format as the returned data for aspectRatio are numbers like 0.6666666666666666 for portrait images or 1.5003750937734435 for landscape. Having those classes mentioned above would be better to work with; landscape, portrait or square.

This is how I'm getting all my gallery images from the current post, along with their aspectRatio.

export default ({ data }) => {
  return (
    <Layout>
      <article>
        {data.allFile.edges.map(({ node }, index) => (
          <div>
            <Img
              key={index}
              className={node.childImageSharp.fluid.aspectRatio}
              alt={node.name}
              fluid={node.childImageSharp.fluid}
            />
            <span>{node.childImageSharp.fluid.aspectRatio}</span>
          </div>
        ))}
      </article>
    </Layout>
  );
};

The full GraphQL query I am using is:

export const query = graphql`
  query($slug: String!, $absolutePathRegex: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
        date
        modified
        caption
        description
        cover {
          publicURL
          childImageSharp {
            fluid(maxWidth: 915, quality: 90) {
              ...GatsbyImageSharpFluid_withWebp
            }
          }
        }
      }
      fields {
        slug
      }
    }
    allFile(
      filter: {
        extension: { regex: "/(jpg)|(png)|(tif)|(tiff)|(webp)|(jpeg)/" }
        absolutePath: { regex: $absolutePathRegex }
      }
    ) {
      edges {
        node {
          name
          childImageSharp {
            fluid(maxWidth: 915, quality: 90) {
              aspectRatio
              ...GatsbyImageSharpFluid_withWebp
            }
          }
        }
      }
    }
  }
`;

There must be a simple solution to this using a conditional statement in React where you map over all your images, take the aspect ratio and then convert the raw data to the desired classes.

So instead of:

<div class="1.5003750937734435 gatsby-image-wrapper"></div>
<div class="0.6666666666666666 gatsby-image-wrapper"></div>
<div class="0.6666666666666666 gatsby-image-wrapper"></div>
<div class="1.0000000000000000 gatsby-image-wrapper"></div>
<div class="1.5003750937734435 gatsby-image-wrapper"></div>

You'd get:

<div class="landscape gatsby-image-wrapper"></div>
<div class="portrait gatsby-image-wrapper"></div>
<div class="portrait gatsby-image-wrapper"></div>
<div class="square gatsby-image-wrapper"></div>
<div class="landscape gatsby-image-wrapper"></div>

Which could then be easily styled with css.

like image 571
Glen Avatar asked Oct 15 '22 13:10

Glen


1 Answers

Unless I'm missing something, aren't you already 99% there? You can write your conditional inside of the map, or better yet, write a component that wrap <Img>:

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

// we only care about `aspectRatio`, the rest will be passed directly to `Img`
// also take out `className` so it be merged with our generated `orientation` class name
const ImgWithOrient = ({ aspectRatio, className, ...props }) => {
  let orientation
  if (aspectRatio > 1) orientation = 'landscape'
  if (aspectRatio < 1) orientation = 'portrait'
  else orientation = 'square'

  return <Img className={`${className} ${orientation}`} {...props} />
}

export default ({ data }) => {
  return (
    <Layout>
      <article>
        {data.allFile.edges.map(({ node }, index) => (
          <div key={index}>
            <ImgWithOrient
              key={index}
              aspectRatio={node.childImageSharp.fluid.aspectRatio}
              className="other class name"
              alt={node.name}
              fluid={node.childImageSharp.fluid}
            />
            <span>{node.childImageSharp.fluid.aspectRatio}</span>
          </div>
        ))}
      </article>
    </Layout>
  )
}

Also note that when you loop over something, you'd need to put key in the most outer component -- in this case, the outer <div>, instead of <Img>.

like image 110
Derek Nguyen Avatar answered Nov 15 '22 04:11

Derek Nguyen