Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gatsby-plugin-sharp] Fluid image with specified aspect ratio #11851

Closed
missmatsuko opened this issue Feb 18, 2019 · 23 comments
Closed

[gatsby-plugin-sharp] Fluid image with specified aspect ratio #11851

missmatsuko opened this issue Feb 18, 2019 · 23 comments
Labels
type: question or discussion Issue discussing or asking a question about Gatsby

Comments

@missmatsuko
Copy link
Contributor

Summary

Is there a way to force a crop/aspect ratio using fluid?
I want to create a set of image cropped to squares to use as thumbnails.
I could use resize, but that only provides one src with the exact height and width provided.
I could use fixed, which is a bit better because it creates srcs for different pixel densities, but that doesn't consider the image could be displayed at different sizes depending on the viewport.

Relevant information

https://www.gatsbyjs.org/packages/gatsby-plugin-sharp/

Environment (if relevant)

File contents (if changed)

gatsby-config.js: N/A
package.json: N/A
gatsby-node.js: N/A
gatsby-browser.js: N/A
gatsby-ssr.js: N/A

@wKovacs64
Copy link
Contributor

You could probably just trample fluid's aspectRatio with your own:

<Img fluid={{ ...image.fluid, aspectRatio: 1 }} />

@missmatsuko
Copy link
Contributor Author

That will change how the image is displayed with CSS, but not the aspect ratio of the actual image file. So it kind of works, but then I'm probably going to load a bigger image than I need to, so part of it gets hidden rather than an actual cropped image.

@wardpeet wardpeet added the type: question or discussion Issue discussing or asking a question about Gatsby label Feb 18, 2019
@wardpeet
Copy link
Contributor

At the moment there is not a real fix for this I guess you could simulate it with multiple GraphQL queries?

query {
    placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
      childImageSharp {
        resize(width: 300, height: 250) {
          src
          width
          height
        }
      }
    }

    placeholderImageBig: file(relativePath: { eq: "gatsby-astronaut.png" }) {
      childImageSharp {
        resize(width: 600, height: 500) {
          src
          width
          height
        }
      }
    }
  }

If you can give us some more context and perhaps a reproduction that might help us answer your issue.

@wardpeet wardpeet added status: needs more info Needs triaging and reproducible examples or more information to be resolved status: needs reproduction This issue needs a simplified reproduction of the bug for further troubleshooting. labels Feb 18, 2019
@missmatsuko
Copy link
Contributor Author

Hi @wardpeet

I'm starting to think this may be more of a feature request than a question, then.

I'm wanting to get a set of images (I guess it's called GatsbyImageSharpFluid ?) to pass into a gatsby-img component. I think creating multiple images with resize will make it more challenging to use gatsby-img since I can't pass the result in directly.

Fluid has options for maxWidth and maxHeight, to constrain the image dimensions, but there doesn't seem to be a way to crop the image to a desired aspect ratio.

One use case I can think of is a gallery of images with thumbnails. I'd like to produce square thumbnails from the original images which can be clicked to view the full image. The thumbnail grid is responsive, so I don't want to use fixed or resize. I'd also prefer not have to manually create the different crops of the same image.

Here's an example of the kind of thing it would be good for, in case my explanation wasn't very clear: https://www.zomato.com/vancouver/suika-fairview-vancouver/photos

@gatsbot gatsbot bot added the stale? Issue that may be closed soon due to the original author not responding any more. label Mar 11, 2019
@gatsbot
Copy link

gatsbot bot commented Mar 11, 2019

Hiya!

This issue has gone quiet. Spooky quiet. 👻

We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here.

If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!

Thanks for being a part of the Gatsby community! 💪💜

@missmatsuko
Copy link
Contributor Author

This issue is not stale.

@lannonbr lannonbr added not stale and removed stale? Issue that may be closed soon due to the original author not responding any more. labels Mar 11, 2019
@missmatsuko
Copy link
Contributor Author

I don't know if these labels apply anymore, either:

status: needs more info
status: needs reproduction
type: question or discussion

I think this is a feature request now.

@missmatsuko missmatsuko changed the title Fluid image with specified aspect ratio [gatsby-plugin-sharp] Fluid image with specified aspect ratio Mar 14, 2019
@VGoose
Copy link
Contributor

VGoose commented Mar 29, 2019

So I did some digging, and you can set aspect ratio with fluid. It's not explicitly stated in the docs but if you set both maxWidth and maxHeight, it will respect the ratio.

When both options are set, the width of each image is set to maxWidth * breakpoint factor (size).
And the height is set to width * maxHeight/maxWidth.

  //line 442 of gatsby-plugin-sharp/src/index.js
  const dimensionAttr = fixedDimension === `maxWidth` ? `width` : `height`
  const otherDimensionAttr = fixedDimension === `maxWidth` ? `height` : `width`
  const images = sortedSizes.map(size => {
    const arrrgs = {
      ...options,
      [otherDimensionAttr]: undefined,
      //WIDTH SET HERE
      [dimensionAttr]: Math.round(size),  
    }
    //IF BOTH MAXWIDTH AND MAXHEIGHT SET
    if (options.maxWidth !== undefined && options.maxHeight !== undefined) {
      //HEIGHT SET HERE
      arrrgs.height = Math.round(size * (options.maxHeight / options.maxWidth))
    }

    return queueImageResizing({
      file,
      args: arrrgs, // matey
      reporter,
    })
  })

Should we update the docs to explicitly state this? I previously thought this wasn't possible as well.

@missmatsuko
Copy link
Contributor Author

missmatsuko commented Mar 30, 2019

@VGoose Will that only work if the source image is larger than the maxWidth and maxHeight?

I thought that maxWidth and maxHeight was meant to set an upper bounds on the image dimensions. If it's being used to set the aspect ratio... that seems like unexpected behaviour actually. What I expected fluid to do with maxWidth and maxHeight is that all the images in the source set are still the natural aspect ratio of the image, but the largest image in the set will not be wider than maxWidth or taller than maxHeight.

@VGoose
Copy link
Contributor

VGoose commented Mar 30, 2019

It works when maxWidth and/or maxHeight is larger than the source size, too.

maxWidth & maxHeight sets the default src break points:

  //line 366 of gatsby-plugin-sharp/src/index.js
  const fixedDimension =
  options.maxWidth === undefined ? `maxHeight` : `maxWidth`   

  ...

  //line 409
  if (!options.srcSetBreakpoints || !options.srcSetBreakpoints.length) {
    fluidSizes.push(options[fixedDimension] / 4)
    fluidSizes.push(options[fixedDimension] / 2)
    fluidSizes.push(options[fixedDimension] * 1.5)
    fluidSizes.push(options[fixedDimension] * 2)
    fluidSizes.push(options[fixedDimension] * 3)
  }

And the upper bound is actually set by the actual image dimensions.

  //line 429 of gatsby-plugin-sharp/src/index.js
  const filteredSizes = fluidSizes.filter(
    size => size < (fixedDimension === `maxWidth` ? width : height)
  )
  // Add the original image
  filteredSizes.push(fixedDimension === `maxWidth` ? width : height)

The current parameter names are definitely misleading.

@missmatsuko
Copy link
Contributor Author

Hmm... I'm wondering if there's a situation where someone might actually want the behaviour I thought setting both maxHeight and maxWidth did...

@VGoose
Copy link
Contributor

VGoose commented Mar 30, 2019

maxWidth and maxHeight does influence the size attribute of the img tag if sizes is not specified (not mentioned in the docs as a parameter).

  //line 393 gatsby-plugin-sharp/src/index.js 
  //presentationWidth is derived from maxWidth or maxHeight
  if (!options.sizes) {
    options.sizes = `(max-width: ${presentationWidth}px) 100vw, ${presentationWidth}px`
  }

This tells the browser for screen sizes beyond presetationWidth, use the srcset image closest to presentationWidth.

I'm not sure if browsers respects this, though. For example if your presentationWidth is 400px, and there's an 800px img in the srcset what happens for screen widths > 600px?

@tremby
Copy link
Contributor

tremby commented Mar 30, 2019

This is something I've found is missing in the past too.

@missmatsuko

Hmm... I'm wondering if there's a situation where someone might actually want the behaviour I thought setting both maxHeight and maxWidth did...

If I understand you correctly, then absolutely.

Say my design is to have a grid of squares. It's elastic. There might be different numbers of columns depending on the viewport width, and the squares may be different sizes depending on the viewport width. That means that "fluid" is what we want, if I understand all the options correctly.

Now say we have a set of images we want to display in these squares. The images are at various aspect ratios.

I might want them not to be cropped, for them all to be contained within their squares. My CSS will centre each within its container. In this case I'd want maxHeight and maxWidth to mean that the image should be resized so the presentation height and width both fit within these constraints, without cropping. The images generated should all have the original aspect ratio.

kittens1

The above is what I would expect to happen if I set both maxHeight and maxWidth to the same value, and my styles set the element to be contained. I'd expect there to be generated a set of images each with the original aspect ratio, at various sizes. I set up the sizes parameter to fit my design, so that the browser will choose the appropriate file for the current viewport size.

Or I might want to display them in the same grid but with all of them cropped to be square. In this case I'd expect to still send maxHeight and maxWidth as the same value, but to also ask for it to be cropped. That might be called "crop" or "cover" or similar. And then I'd want it to keep the intrinsic aspect ratio (i.e. nothing gets stretched or squished) but crop the images (perhaps with more parameters to choose where the gravity is) so they fit in the aspect I've chosen, derived from the height and width parameters, with the nominal presentation size being the height and width I specified.

kittens2

If this is what happens currently when you use maxHeight and maxWidth together, to me that seems unexpected. If I didn't ask for cropping specifically, I don't expect it to do any. Not on the server side (i.e. baked into the images), nor on the client side (as part of gatsby-image, using object-fit: cover or similar). If this is indeed what is happening, which of those is it?

@VGoose

This tells the browser for screen sizes beyond presetationWidth, use the srcset image closest to presentationWidth.

I'm not sure if browsers respects this, though. For example if your presentationWidth is 400px, and there's an 800px img in the srcset what happens for screen widths > 600px?

I can't speak for all browsers, and the spec document is very long and I couldn't find what I wanted, but I just did a little test in Firefox, and Firefox will choose the smallest item from the srcset whose advertised width is the same as or greater than the matching slot found in sizes, or otherwise the biggest one available. For example, with this HTML:

<img src="http://placehold.it/100x100?text=src" sizes="100vw" srcset="http://placehold.it/100x100 100w, http://placehold.it/200x200 200w, http://placehold.it/300x300 300w, http://placehold.it/400x400 400w">

there is only one sizes slot, 100vw, so it always looks for an entry with an advertised width of whatever the viewport width is. With that example HTML you can resize the viewport (eg in responsive design mode) and you'll see that as soon as you're 101px wide it'll load the 200w version, as soon as you're 201px wide it'll load the 300w version, etc.

(I believe browsers aren't strictly required to follow this. If a browser already loaded the 400w version it may not bother making a new request for the 300w version should the viewport get narrower.)

But I don't think this talk about sizes is really relevant. As the developer you're going to be manually specifying the sizes attribute in all but the most basic cases, since you're the only one who really knows how your image behaves on screen.

@VGoose
Copy link
Contributor

VGoose commented Mar 31, 2019

I made a bug for this #12972

@missmatsuko there's a way to get the behavior you want. You have to overwrite the width and height of the img created by gatsby-image Img .

<Img ..... imgStyle={{width: 'auto'; height: 'auto'}}  />

This will get the browser to respect the sizes attribute i.e your maxHeight/maxWidth.

@tremby
Copy link
Contributor

tremby commented Mar 31, 2019

@VGoose I don't think there's any issue with the sizes attribute. As long as it has any width descriptors at all, sizes does not control how large an image will appear on screen, only which image file from the srcset is loaded.

There may or may not be an issue with the default width and height applied to the img element, but I don't think that's really the point. The developer will absolutely be setting styles on the img tag or gatsby-image's wrappers if necessary.

But the crux of this, I think, is to do with how gatsby-plugin-sharp interprets its arguments, and to do with which images it then chooses to generate. These images are what's fed into your img tag's srcset, and they need to be what you're expecting for the width/height/sizes attributes you're configuring it with to function as you intend.

I ran a test and when setting maxHeight and maxWidth on a call to fluid the output image assets are indeed cropped to the centre portion, so even though it's weird behaviour, this ticket is probably ready to be closed and superseded by the ticket you opened about the weirdness.

I did notice, however, that the placeholder thumbnail image is not cropped -- it's coming out as 20x20. That, I think, is a bug.

@tremby tremby removed status: needs more info Needs triaging and reproducible examples or more information to be resolved status: needs reproduction This issue needs a simplified reproduction of the bug for further troubleshooting. labels Mar 31, 2019
@VGoose
Copy link
Contributor

VGoose commented Mar 31, 2019

@tremby OK now that I understand it a little better I can answer your previous questions.

If this is what happens currently when you use maxHeight and maxWidth together, to me that seems unexpected. If I didn't ask for cropping specifically, I don't expect it to do any. Not on the server side (i.e. baked into the images), nor on the client side (as part of gatsby-image, using object-fit: cover or similar). If this is indeed what is happening, which of those is it?

It's happening server side.

But I don't think this talk about sizes is really relevant. As the developer you're going to be manually specifying the sizes attribute in all but the most basic cases, since you're the only one who really knows how your image behaves on screen.

If you're using gatsby-image, you can't alter the sizes attribute of the img element. Sizes can be passed as an to option fluid but not documented - this is in the source code.

  //line 392
  // If the users didn't set default sizes, we'll make one.
  if (!options.sizes) {
    options.sizes = `(max-width: ${presentationWidth}px) 100vw, ${presentationWidth}px`
  }

But the crux of this, I think, is to do with how gatsby-plugin-sharp interprets its arguments, and to do with which images it then chooses to generate. These images are what's fed into your img tag's srcset, and they need to be what you're expecting for the width/height/sizes attributes you're configuring it with to function as you intend.

Completely agree.

I did notice, however, that the placeholder thumbnail image is not cropped -- it's coming out as 20x20. That, I think, is a bug.

Yes, I think this read this in another issue. I'll try and find it.

Should I document all this in #12972?

@tremby
Copy link
Contributor

tremby commented Apr 1, 2019

If you're using gatsby-image, you can't alter the sizes attribute of the img element. Sizes can be passed as an to option fluid but not documented - this is in the source code.

Yeah, that should really be documented. I had a look to see if information about it would come up in the graphql browser. The property shows up, but no information about it.

Yes, you can set the sizes attribute in the call to fluid or you could hack it into your gatsby-image such as <Img fluid={{ ...data.placeholderImage.childImageSharp.fluid, sizes: '(max-width: 512px) 20vw, (max-width: 768px) 35vw, 430px' }} />.

Should I document all this in #12972?

Be my guest.

I'm going to close this one since the functionality being asked for is currently possible.

@tremby tremby closed this as completed Apr 1, 2019
@wardpeet
Copy link
Contributor

wardpeet commented Apr 1, 2019

@VGoose please document it 👍

@lunelson
Copy link

lunelson commented Jun 4, 2020

I searched quite a while for this, and ironically noticed later that the maxWidth / maxHeight method is indirectly documented on this gatsby docs page about defining image options through graphQL fragments. Their example looks like this

  fragment squareImage on File {
    childImageSharp {
      fluid(maxWidth: 200, maxHeight: 200) {
        ...GatsbyImageSharpFluid
      }
    }
  }

https://www.gatsbyjs.org/docs/working-with-images/#using-fragments-to-standardize-formatting

@gxwheel152
Copy link

I was using the method referenced above by @lunelson, but am now getting an error:

in GraphiQL:
"GraphQLError: Unknown fragment \"GatsbyImageSharpFluid\".",

Any thoughts ?

@lunelson
Copy link

@gxwheel152 are you getting this error in the GraphQL Explorer? I think those fragments only work in Gatsby...

@gxwheel152
Copy link

I was having trouble using it in components that were not pages --- I switched to this and then it worked in the components as well.

query { file(name: { regex: "/wrestling_Friend_450x450/" }) { childImageSharp { fluid(maxWidth: 200, maxHeight: 200) { aspectRatio base64 sizes src srcSet } } } }

@pvaladez
Copy link
Contributor

On using the GatsbyImageSharpFluid fragment in graphiQL, or the GraphQL explorer:

It looks like they should add support for that in the near future, but...

If you want, you can just paste in the definition of the fragment into graphiQL outside of your query. The fragments are defined in node_modules/gatsby-transformer-sharp/fragments.js -- or just search "fragment GatsbyImageSharpFluid" in VS code with the "files to exclude" option unchecked

  fragment GatsbyImageSharpFluid on ImageSharpFluid {
    base64
    aspectRatio
    src
    srcSet
    sizes
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: question or discussion Issue discussing or asking a question about Gatsby
Projects
None yet
Development

No branches or pull requests

10 participants