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

[Image] Forward focus coordinates to object-position #21

Open
Dangoo opened this issue Jul 22, 2021 · 2 comments
Open

[Image] Forward focus coordinates to object-position #21

Dangoo opened this issue Jul 22, 2021 · 2 comments

Comments

@Dangoo
Copy link
Contributor

Dangoo commented Jul 22, 2021

Is your feature request related to a problem? Please describe.
At the time of writing the image component uses the focus parameter only for passing it to the Storyblok API which works well for images with dimensions that are not relative to their container.
So imagine you have lets say a stage module which is width: 100vw; and height: 80vh; and the image should fill the whole area (similar to next/images layout="fill"):

<section className="stage" style={{ width: '100vw', height: '80vh' }}>
  <Image {...imageProps} fluid={1920} width="100%" height="100%" />
  {...children}
</section>

Depending on the aspect ratio of the original image the Storyblok API tries to shift image to the provided focus as good as it can, but then the Images CSS applies object-position: center center which can lead to a scenario where the focus point is not even in the visible area of the component.

Describe the solution you'd like
What would you think about passing the focus coordinates down to the CSS to make sure the focus point/area is always visible?
Two things are to be considered:

  • Storyblok treats focus as area described by two pairs of coordinates while object-position expects a point which would make calculating the center of the focus area necessary (e.g. by <coordinateA> + <coordinateB> / 2)
  • Shifting the image using object-position by a percentage value can lead to moving it too far and create undesired whitespace so the amount of translation has to be clamped:
    const pictureStyles: CSSProperties = {
      /* ... */
      objectFit: fit,
      objectPosition: `clamp(0%, ${focusX}, 100%) clamp(0%, ${focusY}, 100%)',
    };

Describe alternatives you've considered
For now we tried to patch the Image component from the outside to reflect the desired behavior, but this is not really a sustainable solution…

const getFocusPoint = (focus: string) =>
  focus
    .split(':')[0]
    .split('x')
    .map((p) => parseInt(p))

const getFocusPosition = (focus: string, src: string) => {
  const [focusPointX, focusPointY] = getFocusPoint(focus)
  const { width, height } = getImageProps(src)
  const positionX = Math.floor(
    Math.min(Math.max((focusPointX / width) * 100, 0), 100)
  )
  const positionY = Math.floor(
    Math.min(Math.max((focusPointY / height) * 100, 0), 100)
  )
  return [`${positionX}%`, `${positionY}%`]
}

export const PatchedImage: React.FC<ImageProps> = ({ focus, src, ...restProps }) => {
  const imageContainerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (!focus || !src || !imageContainerRef?.current) {
      return
    }
    const [positionX, positionY] = getFocusPosition(focus, src)
    const images = imageContainerRef.current.querySelectorAll('img')
    for (const image of images) {
      image.style.objectPosition = `${positionX} ${positionY}`
    }
  }, [focus, src])

  return (
    <div ref={imageContainerRef} style={{ display: 'contents' }}>
      <Image src={src} focus={focus} {...restProps} />
    </div>
  )
}

Let me know what you think, happy to discuss the details or helping with the implementation :)

/cc @martinjuhasz

@BJvdA
Copy link
Member

BJvdA commented Jul 22, 2021

Thanks for the well-written issue! I think this is indeed something that this library needs, actually when implementing focus points I was considering doing it like this but didn't know clamp was a thing.
Interestingly when testing I found that the focus point that storyblok gives back is actually two pairs of coordinates that are right next to eachother (1px difference).
I'll try to implement the object-fit behaviour you described.

@Dangoo
Copy link
Contributor Author

Dangoo commented Jul 22, 2021

@BJvdA thank you for looking into this!
We discovered the same and our assumption is that with the two types of predefined image blocks led to this focus API:

  • "Image (old)" has an image editor with predefined aspect ratios where I'd expect focus being the selected area
  • "Media" interface provides only a point where the focus area is 1x1 px in size to approximate point

clamp is a rather new feature where depending on the targeted browsers a fallback using the min / max equivalent: max(MIN, min(VAL, MAX)) might extend support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants