Search code examples
css

Image needs to maintain width between 50-100% and scroll if aspect ratio is greater than 1:2


I am using react-bootstrap and have a <Carousel> that I am using to display images. I'd like for them to be contained within the container, such that the entire image is visible, except for in the case that the aspect ratio is greater than 1:2 (ie really tall images), in which case you can scroll to see the rest of the image. Here is the markup for the carousel:

          
      <Col xs="6" className="image-carousel-container">
        <Carousel
          activeIndex={activeIndex}
          onSelect={handleSelect}
          interval={null}
          className="image-carousel"
        >
          {orderItem.imageNames.map((imageName, index) => (
            <Carousel.Item key={index}>
              <div className="image-container">
                <img
                  className="image-carousel-image"
                  src={apiEndpoint + PATH.TEMP_IMAGES + imageName}
                  alt={imageName}
                />
              </div>
            </Carousel.Item>
          ))}
        </Carousel>
        <p className="image-count-text">{`Total Images: ${orderItem.imageNames.length}`}</p>
      </Col>

And the CSS I have defined:


    .image-carousel-container {
      display: grid;
      flex-direction: column;
      margin: auto;
      min-height: 100%;
      height: auto;
    }
    
    .image-carousel {
      max-width: 100%;
      max-height: 60vh;
      display: flex;
      align-items: center;
    }
    
    .image-container {
      min-height: 60vh;
      width: auto;
      margin: auto;
      overflow-y: auto;
    }
    
    .image-carousel-image {
      max-width: 100%;
      min-width: 50% !important;
      height: 60vh;
      object-fit: contain;
    }


So you can see I am attempting to make these "tall images" take up half the width of the container, and then scroll to see the rest of the image.

With this configuration, everything works except for the scroll. The min-width appears to be ignored, and the tall images are just shrunk to fit the container.

If I switch object-fit: cover, then the tall images take up half the width, but I have 2 issues:

  1. my "wide" images expand to fill the container vertically (I want a max width of 100%).

  2. the top and bottom are just truncated, there is no scroll available.

Is there a way to get the images to just fit the container and only scroll when necessary (while still taking up half the width of the container)? I have been struggling with this for a while now and none of the existing SO questions appear to address this specific case.


Solution

  • I wasn't able to find a purely CSS solution to this problem, but I was able to achieve the desired result with some custom JavaScript.

    I first wrapped the image within the <Carousel.Item> inside of a new <div> with class .image-container:

    
          {orderItem.imageNames.map((imageName, index) => (
            <Carousel.Item key={index}>
              <div
                className="image-container"
              >
                <img
                  className="image-carousel-image"
                  src={apiEndpoint + PATH.TEMP_IMAGES + imageName}
                  alt={imageName}
                  onLoad={checkOverflow}
                />
              </div>
            </Carousel.Item>
          ))}
    
    

    I then apply the following styles to "normal" images that aren't tall enough to require scrolling:

    
        .image-container {
          height: 60vh;
          width: 100%;
          margin: auto;
          overflow-y: auto;
        }
        
        .image-carousel-image {
          width: auto !important;
          max-width: 100%;
          height: 60vh;
          overflow-y: auto;
          object-fit: contain;
        }
    
    

    Then, by searching the document for image-container and image-carousel-image elements, I compare the width of both the container and image, and selectively apply a .tall-image-carousel-image class if needed:

    
        const checkOverflow = () => {
          const containers = document.querySelectorAll(".image-container");
        
          setTimeout(() => {
            containers.forEach((container) => {
              const image = container.querySelector(
                ".image-carousel-image" || ".tall-image-carousel-image"
              );
              if (image && container) {
                if (container.clientWidth * 0.5 > image.clientWidth) {
                  image.classList.remove("image-carousel-image");
                  image.classList.add("tall-image-carousel-image");
                } else {
                  // Reset to default values
                  image.classList.add("image-carousel-image");
                  image.classList.remove("tall-image-carousel-image");
                }
              }
            });
          }, 50);
        };
    
    
    
        .tall-image-carousel-image {
          width: 50% !important;
          max-width: 100%;
          height: auto !important;
          overflow-y: auto;
          object-fit: contain;
        }
    
    

    Note that I use a setTimeout() to ensure the image is rendered and actually has a width by the time it is checked.

    I then call this checkOverflow function in the onLoad of the <img> (as shown in the first code snippet), and also add the following useEffect to add event listeners to check again when necessary:

    
          useEffect(() => {
            window.addEventListener("load", checkOverflow);
            window.addEventListener("resize", checkOverflow);
        
            // Cleanup function to remove the event listeners
            return () => {
              window.removeEventListener("load", checkOverflow);
              window.removeEventListener("resize", checkOverflow);
            };
          }, []);