Trying to create auto-resizing horizontally-scrollable background image

I’m trying to recreate this webpage, and I’m looking for advice on my approach.

What I’m trying to achieve:

  1. A background image with a height that always matches the screen height while preserving its aspect ratio.
  2. Additionally, I want the image to be horizontally scrollable when it’s wider than the device screen.
  3. Clickable hotspots that remain fixed on particular positions on the background image, regardless of screen size.

Here’s my current setup: CodeSandBox

export const Bedroom = ({ blurDataURL, hotspots }: BedroomProps) => {
  return (
    <section id="bedroom">
      <div className="h-screen w-auto relative ">
        {/* I aim to resize this image so that its height matches the screen height while preserving its aspect ratio. Additionally, I want the image to be horizontally scrollable when it's wider than the device screen. Ideally, I would also remove the object-cover property as it woulld affect the hotspots' positioning.  My tech stack includes Next.js, Tailwind CSS, and Typescript. */}
        <Image
          src={src}
          alt={title}
          placeholder="blur"
          blurDataURL={blurDataURL}
          width={4000}
          height={2500}
          className={cn("h-full w-full object-cover")}
        />
        {hotspots.map((hotspot, index) => (
          <div
            key={index}
            className="absolute"
            style={{
              left: hotspot?.position?.left,
              top: hotspot?.position?.top,
              transform: "translate(-50%, -50%)",
            }}
          >
            <Popover>
              <PopoverTrigger asChild>
                <div className="relative size-10 flex items-center justify-center cursor-pointer">
                  <div className="bg-white rounded-full p-3 animate-slow-ping opacity-65" />
                  <div className="bg-white rounded-full p-1 animate-none shadow absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-2" />
                </div>
              </PopoverTrigger>
              <PopoverContent
                className={cn(
                  GeistSans.className,
                  "rounded-full capitalize text-sm lg:text-base font-medium tracking-tight px-4 py-1.5"
                )}
              >
                <a href={hotspot?.href}>
                  <div>{hotspot?.name}</div>
                </a>
              </PopoverContent>
            </Popover>
          </div>
        ))}
      </div>
    </section>
  );
};

In the original approach, the image was set to fill the height of the screen (h-full) with the object-cover property, which caused the image to stretch or crop while maintaining its aspect ratio. However, the issue arose because this method didn’t make the image horizontally scrollable when wider than the screen. The result was a non-scrollable image that didn’t preserve the desired behavior.

The next approach involved removing the object-cover property and setting w-auto to ensure the image scales automatically based on its width, allowing horizontal scrolling. This attempt did not preserve the image’s aspect ratio or achieve horizontal scrollability or proper overflow. The image still stretched vertically without allowing horizontal movement.

Another approach attempted to wrap the image in a container with explicit width and height controls (min-w-full, h-screen). Similar results were achieved

Did you also set the overflow-x property to allow the horizontal scroll?

Also note that when trying to preserve aspect-ratio, you should not explicitly set both the width and height i believe. Just set one and let the other go to auto.

Thank you for replying @hbar1st,

I did set the overflow-x property on the container div and I only set the height of the image, I didn’t set the width.

One other thought is to use a container around the image with display flex and with the overflow-x set and use the height setting only on the image with aspect-ratio set to auto.

Do you have a codepen or a GitHub pages or other live version of your code?

Hi @hbar1st ,

I’ve tried that as well but the image doesn’t overflow.

Here’s a Codesandbox:

https://codesandbox.io/p/github/joshuaedo/shop-josh-next/bedroom?file=%2Ffeatures%2Fbedroom%2Fcomponents%2Fbedroom.tsx

do you have a copy of the image to upload here? I’m having a hard time figuring out what is happening because you have a lot going on in that page. (or if the image is too big, please upload it on github so I can get it from there)

1 Like

@hbar1st — sorry for breaking in here, I think I managed to find a copy of the image to upload here for you to use after digging around in my Web Inspector.


Hope that helps a bit!

1 Like

Hi and welcome to the forum!

While I don’t exactly understand your code, I have an idea on how you could do it.

width: clamp(800px, 100vw, 100vw);

What this basically does is make the image the width of the viewport until it’s 800px, in which case it’ll stop scaling.

in a completely simplified environment, I can make the image fit the height of the view area while scrolling to the right (and keeping its aspect ratio) like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            box-sizing: border-width;
        }
        body {
            overflow-x: scroll;
        }
        img {
            display: block;
            min-height: 100dvh;
            max-height: 100dvh;
        }
    </style>
</head>
<body>
    <img src="image.jpeg" alt="image" width="1380" height="862">
</body>
</html>

However on my screen, this image is small and is not big enough to fill the screen when the browser is at full size. So in order to test, I made my browser small enough to make it scroll sideways.

I don’t have access to your image (I’m assuming it is bigger than the one I used), but this should work for your image too provided that no other rules are in effect. Obviously this is a big proviso. Your page is much more complicated but I just wanted to show that this is actually all it takes to get the sideways scrolling to work.

1 Like

Yes, as @hbar1st pointed out, it’s quite easy to implement. Here’s my solution too if you’re interested, fulfilling all of your conditions:

However, you’ll notice the image has extra space on the right when the viewport is wider than the image. That is because the image is set to always be the same height as the viewport, even if the aspect ratio of the viewport is wider than the image’s aspect ratio. How to solve this problem? By setting the width to be the same as the viewport’s instead of the height. Here’s a pen with this new solution implemented: (read the CSS comments for explanation)

Hope this was something like what you were asking for.

Thanks so much for your help!

I tried playing around with your solutions, but ultimately I couldn’t make the image stay at screen height while maintaining the original aspect ratio using your fixes.

The size of the image played a big part in this as @hbar1st predicted.

After a LOT of debugging, I discovered that the Lenis instance I initialized was the culprit preventing me from horizontally scrolling.

I didn’t notice this earlier because this particular page on my app is meant to be 100svh, and the other pages did not need horizontal scrolling.

After implementing a route check for the instance, I was able to come up with this, largely inspired by the original webpage:


<section id='bedroom'>
      <div
        className='relative overflow-x-auto overflow-y-hidden' 
      >
        <div className='relative h-[100svh] aspect-[16/10] lg:w-full'>
          <picture className='h-[100svh] aspect-[16/10] lg:object-cover object-center'>
            <source media='(min-width: 1024px)' srcSet={`${src}?w=4000`} />
            <source media='(min-height: 600px)' srcSet={`${src}?w=2560`} />
            <source media='(min-height: 500px)' srcSet={`${src}?w=1920`} />
            <Image
              src={`${src}?w=1024`}
              alt={title}
              placeholder='blur'
              blurDataURL={blurDataURL}
              width={4000}
              height={2500}
              className='h-[100svh] aspect-[16/10] lg:object-cover'
              priority
            />
          </picture>
          <div className='absolute inset-0'>
            {hotspots.map((hotspot, index) => (
              <BedroomHotspot
                hotspot={hotspot}
                key={index}
              />
            ))}
          </div>
        </div>
      </div>
    </section>

It isn’t particularly as straightforward as the fixes you suggested but ultimately it did the job.

Just like in @nickrg 's fix, on screens larger than 1024px,

You’ll notice the image has extra space on the right when the viewport is wider than the image

Either width: 100% and width: 100vw was sufficient to fix that.

Thank you again!

2 Likes

Glad it worked. (this post must be at least 20 characters long)