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:
my "wide" images expand to fill the container vertically (I want a max width of 100%).
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.
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);
};
}, []);