Giter Site home page Giter Site logo

Comments (25)

jenkshields avatar jenkshields commented on August 17, 2024 14

There's a way to work around this by passing in a style object that contains an object-position/objectPosition key!

I did it (for a background-image but the same process applies) like this:

const positionStyles = {
    backgroundPositionX: `${data.sanitySiteSettings.image.hotspot.x * 100}%`,
    backgroundPositionY: `${data.sanitySiteSettings.image.hotspot.y * 100}%`,
  }

from gatsby-source-sanity.

hdoro avatar hdoro commented on August 17, 2024 6

As for fixed images, we must crop them properly. Here's the code I came to for doing that (it ignores hotspot as it doesn't make sense in most fixed contexts):

function getFixedWithCrop({ assetId, fixed, crop }) {
  let newFixed = { ...fixed };
  const [, , dimensions] = assetId.split('-');
  // Get the original width and height
  const [width, height] = dimensions.split('x');

  // Let's calculate the rect query string to crop the image
  const { left, top, right, bottom } = crop;
  const effectiveWidth = Math.ceil((1 - left - right) * width);
  const effectiveHeight = Math.ceil((1 - top - bottom) * height);

  // rect=x,y,width,height
  // (each is in absolute PX, that's why we refer to width and height)
  const cropQueryStr = `&rect=${Math.floor(left * width)},${Math.floor(
    top * height,
  )},${effectiveWidth},${effectiveHeight}`;

  /*
    cdn.sanity.io/...?w=100&h=94&fit=crop 1x,
    cdn.sanity.io/...?w=150&h=94&fit=crop 1.5x,
    */
  function addToSrcset(srcSet) {
    return (
      srcSet
        .split(',')
        // Map over each individual declaration (divided by ,)
        .map((declaration) => {
          // And get their URLs for further modification
          const [url, multiplier] = declaration.split(' ');
          return `${url}${cropQueryStr} ${multiplier}`;
        })
        // and finally turn this back into a string
        .join(',')
    );
  }
  
  // Add the rect query string we created to all src declarations
  newFixed.src = fixed.src + cropQueryStr;
  newFixed.srcWebp = fixed.srcWebp + cropQueryStr;
  newFixed.srcSet = addToSrcset(fixed.srcSet);
  newFixed.srcSetWebp = addToSrcset(fixed.srcSetWebp);

  console.log({ fixed, newFixed });

  return newFixed;
}

export const getFixedProps = ({ assetId, crop }, options) => {
  let fixed = getFixedGatsbyImage(assetId, options, sanityConfig);
  // If we have a crop, let's add it to every URL in the fixed object
  if (crop && crop.top) {
    return getFixedWithCrop({ assetId, fixed, crop });
  }
  return fixed;
};

As I come to think of it, probably combining @jenkshields object-position trick for hotspot with this URL-level crop is the best alternative both for fluid and fixed images, as we're saving some bandwidth from the cropped part of the image that doesn't have to be loaded. Plus, we're actually getting the image size we are looking for, as otherwise when cropping with CSS we're scaling the divs to compensate for the cropping.

Will update this if I come across a better solution πŸ™Œ

from gatsby-source-sanity.

Runehm avatar Runehm commented on August 17, 2024 4

Any update on this subject?

from gatsby-source-sanity.

hdoro avatar hdoro commented on August 17, 2024 4

Here's how I'm managing this right now:

The Sanity image tool preview is based entirely on CSS, which is puzzling as the docs recommend dealing with hotspot and crop through @sanity/image-url. However, this is great for us, as we don't have to re-do how gatsby-image-sanity creates srcsets and the likes, we just have to copy the code from [imagetool/src/calculateStyles.js] and apply it to our image components, like so:

export const SanityFluidImage = ({
  assetId,
  fluidOptions,
  hotspot,
  crop,
  className,
  alt,
}) => {
  if (!assetId || !fluidOptions) {
    return null;
  }
  // If you already have the fluid props from GraphQL, skip this step
  const imgFluidProps = getFluidProps(assetId, fluidOptions);

  // If no hotspot or crop, we're good to go with regular GatsbyImage
  if (!hotspot && !crop) {
    return (
      <GatsbyImage alt={alt} fluid={imgFluidProps} className={className} />
    );
  }

  // If we do, however, let's get each element's styles to replicate the hotspot and crop
  const targetStyles = calculateStyles({
    container: {
      aspectRatio: imgFluidProps.aspectRatio,
    },
    image: {
      aspectRatio: imgFluidProps.aspectRatio,
    },
    hotspot,
    crop,
  });

  // Unfortunately, as we need an extra wrapper for the image to apply the crop styles, we recreate a padding div and add it all to a new container div
  return (
    <div style={targetStyles.container}>
      <div aria-hidden="true" style={targetStyles.padding}></div>
      <GatsbyImage
        fluid={imgFluidProps}
        alt={alt}
        className={className}
        // The GatsbyImage wrapper will have the crop styles
        style={targetStyles.crop}
        imgStyle={targetStyles.image}
      />
    </div>
  );
};

This works for most cases, with a big caveat: if the original image is cropped to a width that is lower than the width it should appear on the front-end, the result will be really weird. There are also some cases where the hotspot doesn't seem to work really well, but that's a small price to pay considering the flexibility we're giving editors 😊

The last time calculatedStyles.js was touched was on July 2018 and that code is for the specific purpose of displaying inside of the cropping dialog inside of Sanity. I think we can probably get to a better, more resilient way of taking crop and hotspot into consideration, but I haven't had the time to dive deep on this.

Hope this can help someone, let me know if I can help o/

EDIT: this getFluidProps is my own version of getFluidGatsbyImage that already contains the instantiated @sanity/client ;)

from gatsby-source-sanity.

xyng avatar xyng commented on August 17, 2024 2

For people that might want the same:
If you use the new Gatsby Image handling and would like hotspots, you can simply extend the Type that contains the asset.

getGatsbyImageData already accepts an Object that has Keys asset, hotspot and crop, you can simply throw your wrapping type in there.
To make things easier (and make gatsby-node handle things) extend your Type that contains the asset (in my case SanityImage) with the field gatsbyImageData.

Would look something like this:

const { getGatsbyImageData } = require('gatsby-source-sanity')
const { getGatsbyImageFieldConfig } = require('gatsby-plugin-image/graphql-utils')

exports.createResolvers = ({ createResolvers }) => {
	createResolvers({
		SanityImage: {
			// pretty much copy and paste from here:
			// https://github.com/sanity-io/gatsby-source-sanity/blob/bbe8565c0c639797e25b742df4e1dc120c465108/src/images/extendImageNode.ts#L47
			gatsbyImageData: getGatsbyImageFieldConfig(
				(image, args) => getGatsbyImageData(image, args, sanityConfig),
				{
					placeholder: {
						type: 'SanityGatsbyImagePlaceholder',
						defaultValue: `dominantColor`,
						// Also copy the description from this line if you want that comment in your schema
						// https://github.com/sanity-io/gatsby-source-sanity/blob/bbe8565c0c639797e25b742df4e1dc120c465108/src/images/extendImageNode.ts#L53
						description: "..."
					},
					fit: {
						type: 'SanityImageFit',
						defaultValue: 'fill',
					},
				},
			),
		},
	})
}

You can then query like this (works the same as the asset field):

fragment ImageWithCropAndHotspot on SanityImage {
	gatsbyImageData(
		layout: FULL_WIDTH
		fit: FILL
		height: 600
	)
}

Only "downside" I noticed so far: GraphQLCodegen exports the type of that field as any - but as long as it works...

from gatsby-source-sanity.

eunjae-lee avatar eunjae-lee commented on August 17, 2024 1

Here is my workaround. I use hotspot only in one component, so I just created a custom image component instead of using gatsby-image. I basically made srcSet on my own and copied&pasted the output of gatsby-image.
If you want to use this kind of workaround with gatsby-image, I guess you could use patch-package.

Plus, the code below is a little specific to my needs.
I have aspectRatio which can be 1, 2/3, 3/2, ...

  const originalUrl = imageUrlFor(buildImageObj(imageNode))
    .width(1440)
    .height(1440 * aspectRatio)  // aspectRatio: 1, 2/3, 3/2, ...
    .url();

  const widths = [360, 720, 1440];
  const srcSetWebp = widths.map(width => {
    const url = `${imageUrlFor(buildImageObj(imageNode))
      .width(width)
      .height(width * aspectRatio)
      .url()}&fm=webp`;
    return `${url} ${width}w`;
  });
  const srcSet = widths.map(width => {
    const url = imageUrlFor(buildImageObj(imageNode))
      .width(width)
      .height(width * aspectRatio)
      .url();
    return `${url} ${width}w`;
  });
  const sizes = `(max-width: 1440px) 100vw, 1440px`;

  return (
    <figure className={className}>
      <div
        sx={{
          position: 'relative',
          overflow: 'hidden',
        }}
      >
        <div
          aria-hidden={true}
          sx={{
            width: '100%',
            paddingBottom: `${100 * aspectRatio}%`,
          }}
        ></div>
        <picture>
          <source type="image/webp" srcSet={srcSetWebp} sizes={sizes} />
          <source srcSet={srcSet} sizes={sizes} />
          <img
            sizes={sizes}
            srcSet={srcSet}
            src={originalUrl}
            alt={imageNode.alt}
            loading="lazy"
            sx={{
              position: 'absolute',
              top: '0px',
              left: '0px',
              width: '100%',
              height: '100%',
              objectFit: 'cover',
              objectPosition: 'center center',
              opacity: 1,
              transition: 'none 0s ease 0s',
            }}
          />
        </picture>
      </div>
    </figure>
  );

from gatsby-source-sanity.

jenkshields avatar jenkshields commented on August 17, 2024 1

@jenkshields sorry, your solution is a bit confusing for me, where do you this code ? in your Sanity schema for the image ?

No, in the front-end - in my case in particular I pulled the x and y information from sanity and used it to position a background image. You can do this by using the x and way to set object-position in your css/style object.

from gatsby-source-sanity.

deodat avatar deodat commented on August 17, 2024 1

Two months later...
Okay, so if dumb guys like me need to be taken by the hand on this, here's how I finally managed to get it working based on @jenkshields solution:

  • First, in my grapql query, I grab the hotspot from Sanity:
  mainImage {
    hotspot {
      x
      y
    }
    asset {
      fluid(
        maxWidth: 1200
      ) {
        ...GatsbySanityImageFluid_noBase64
      }
    }
  }
  • Second, I get it in my component:
export default function BlogPostPreviewList({ mainImage }) {
  const objectPosition = {
    x: `${mainImage.hotspot.x * 100}%`,
    y: `${mainImage.hotspot.y * 100}%`,
  };

  return ( 
    <FirstNodeStyles className="firstPost" objectPosition={objectPosition}>
          {mainImage?.asset && (
            <Img
              fluid={mainImage.asset.fluid}
              alt={mainImage.alt}
            />
          )}
    </FirstNodeStyles>
  );
}
  • Third, in my styled component :
const FirstNodeStyles = styled.div`
  .gatsby-image-wrapper {
    --x: ${(props) => props.objectPosition.x};
    --y: ${(props) => props.objectPosition.y};

    div[aria-hidden='true'] {
      padding-bottom: 41% !important;
    }
    img {
      object-position: var(--x) var(--y) !important;
    }
  }
`;

from gatsby-source-sanity.

schafevormfenster avatar schafevormfenster commented on August 17, 2024

Yes, that would strengthen the sanity/gatsby combination. Not sure if I could support coding, but would join testing and documenting.

from gatsby-source-sanity.

giles-cholmondley-durston avatar giles-cholmondley-durston commented on August 17, 2024

This would be really valuable. My GraphQL site is super-dependent on imagery and am struggling a bit without the crop/hotspot.

from gatsby-source-sanity.

pierrenel avatar pierrenel commented on August 17, 2024

I'm dying to get this to work.. doesn't gatsby image take a styles prop, so you could feed it some x,y values related to the hotspot you get from Sanity?

from gatsby-source-sanity.

amcc avatar amcc commented on August 17, 2024

Adding my vote to this too. Would be very useful.

from gatsby-source-sanity.

 avatar commented on August 17, 2024

Does that also work for crop?

from gatsby-source-sanity.

jenkshields avatar jenkshields commented on August 17, 2024

Does that also work for crop?

I haven't tried personally, but I reckon you should be able to by playing around with object-fit and object-position! It depends on your use case and output, I think.

from gatsby-source-sanity.

kmelve avatar kmelve commented on August 17, 2024

Haha – this is gold @hcavalieri. It would have been cool if we could've made this a bit easier for you, but cool that we know have a way to go about it!

from gatsby-source-sanity.

mellson avatar mellson commented on August 17, 2024

Great work @hcavalieri - good thinking out of the box!

I do something similar where I grab both the rawImage and the fluidImage from GraphQL, and then I use the rawImage to get a url that support crop and hotspot using @sanity/image-url .

Then I pull out the rect part of that url and add it to the fluidImage's srcSet's.

Not the prettiest solution, but until Sanity supports this out of the box, it'll have to do.

The biggest problem I've found with this approach is that you can't use the base64 encoded baseimage because its not cropped.

I can probably clean up my code a bit after looking at your solution @hcavalieri - but just for reference here it is in its current form:

import imageUrlBuilder from "@sanity/image-url"
import { dataset, projectId } from "./sanityConfig"
import { isNil, join, map, pipe, split } from "ramda"

const builder = imageUrlBuilder({ projectId, dataset })
export const sanityImageSrc = (source: any) => builder.image(source)

export const getFluidImage = (
  rawImage: any,
  fluidImage: any,
  width: number,
  height: number
) => {
  const url = sanityImageSrc(rawImage).width(width).height(height).url()!

  const rect = new URL(url).searchParams.get("rect")

  const addRectToUrl = (rect: string | null) => (incomingUrl: string) => {
    if (isNil(rect)) return incomingUrl

    const [url, size] = split(" ")(incomingUrl)
    return `${url}&rect=${rect} ${size}`
  }
  const convertUrl = addRectToUrl(rect)

  const addRectToUrlSet = (rect: string | null) => (incomingUrl: string) =>
    isNil(rect)
      ? incomingUrl
      : pipe(split(","), map(convertUrl), join(","))(incomingUrl)
  const convertUrlSet = addRectToUrlSet(rect)

  return {
    ...fluidImage,
    src: convertUrl(fluidImage.src),
    srcSet: convertUrlSet(fluidImage.srcSet),
    srcSetWebp: convertUrlSet(fluidImage.srcSetWebp),
    srcWebp: convertUrl(fluidImage.srcWebp),
  }
}

from gatsby-source-sanity.

wispyco avatar wispyco commented on August 17, 2024

@hcavalieri Where am I putting this code for the SanityFluidImage component. Somewhere in sanity? Just not sure where?

from gatsby-source-sanity.

viperfx avatar viperfx commented on August 17, 2024

@mellson can you show how this is being used with a component? and a graphql query?

from gatsby-source-sanity.

mellson avatar mellson commented on August 17, 2024

@viperfx sure. The graphql query looks something like this:

query {
    rawImage: sanityDocument {
      _rawMainImage
      mainImage {
        asset {
          fluid(maxWidth: 1152, maxHeight: 420) {
            ...GatsbySanityImageFluid_noBase64
          }
        }
      }
    }
}

and I use it in an image like this:

<Img
  backgroundColor
  fluid={getFluidImage(
    rawImage._rawMainImage,
    rawImage.mainImage.asset.fluid,
    1152,
    420
  )}
/>

from gatsby-source-sanity.

MikeCastillo1 avatar MikeCastillo1 commented on August 17, 2024

maybe this funciton

 const fluidProps = getFluidGatsbyImage(
        props.node.photo.photo,
        {},
        sanity
      )

at this class getGatsbyImageProps.ts could use the crop and hotspot data came from the rawdata and use it, instead of using the arguments of the getFluidGatsbyImage and the default values that comes from them. not sure why the sanity team did that implementation, but I think it can be added the crop and hostpot support here.

What do you think?

from gatsby-source-sanity.

holly-searchengineunity avatar holly-searchengineunity commented on August 17, 2024

@hdoro I would like to integrate your fluid option but I am lost where do you find the calculatedstyles.js?

from gatsby-source-sanity.

deodat avatar deodat commented on August 17, 2024

@holly-searchengineunity it's in Sanity :
node-modules/@sanity/imagetool
that said, I'd really like to see a real implementation of this :)
any codesandbox somewhere ?

from gatsby-source-sanity.

deodat avatar deodat commented on August 17, 2024

@jenkshields sorry, your solution is a bit confusing for me, where do you this code ? in your Sanity schema for the image ?

from gatsby-source-sanity.

deodat avatar deodat commented on August 17, 2024

@jenkshields thanks a lot !

from gatsby-source-sanity.

fderen-dev avatar fderen-dev commented on August 17, 2024

Hey guys, I found small plugin which handles crops and hotspots

https://github.com/coreyward/gatsby-plugin-sanity-image

from gatsby-source-sanity.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.