Warning!!! If you try to do this with a dynamic width, you'll have a problem with empty spaces and that is not our goal.
Ok, what do we need?
- Some pictures for this demo, I'm going to use picsum photos
- React
- Styled components
import * as React from 'react';
import styled from 'styled-components';
export default function LikeMansory(){
return <div>{/*images here*/}</div>
}
For this case I'm using a hardcore list of pictures, but then we can use a dynamic list of pictures like in the real web.
const twentyPictures = [
'https://picsum.photos/id/0/400/450',
'https://picsum.photos/id/1/400/750',
'https://picsum.photos/id/2/400/350',
'https://picsum.photos/id/3/400/550',
'https://picsum.photos/id/4/400/450',
'https://picsum.photos/id/5/400/350',
'https://picsum.photos/id/6/400/350',
'https://picsum.photos/id/7/400/450',
'https://picsum.photos/id/8/400/450',
'https://picsum.photos/id/9/400/450',
'https://picsum.photos/id/10/400/850',
'https://picsum.photos/id/11/400/450',
'https://picsum.photos/id/12/400/450',
'https://picsum.photos/id/13/400/450',
'https://picsum.photos/id/14/400/450',
'https://picsum.photos/id/15/400/450',
'https://picsum.photos/id/16/400/450',
'https://picsum.photos/id/17/400/450',
'https://picsum.photos/id/18/400/700',
'https://picsum.photos/id/19/400/450',
'https://picsum.photos/id/20/400/340',
];
We use our friend map
for this task
...
export default function LikeMansory() {
return (
<div>
{twentyPictures.map((src) => (
<img key={src} src={src} alt="masory-item" />
))}
</div>
);
}
The result have to be something similar to this.
Now this is the first step for this trick. We need to set our layout
...
const Wrapper = styled.div`
display: grid;
grid-gap: 15px;
grid-template-columns: 1fr 1fr 1fr; /* we set 3 columns here */
width: 100%;
max-width: 1200px;
margin: auto;
padding: 50px 0px;
`;
export default function LikeMansory() {
return (
<Wrapper>
{twentyPictures.map((src) => (
<img key={src} src={src} alt="masory-item" />
))}
</Wrapper>
);
}
This is the result for now.
Excellent, now we need to break the default behavior of the rows with this.
...
const Wrapper = styled.div`
display: grid;
grid-gap: 15px;
grid-template-columns: 1fr 1fr 1fr; /* we set 3 columns here */
grid-auto-rows: 0px; /* this is important */
width: 100%;
max-width: 1200px;
margin: auto;
padding: 50px 0px;
`;
...
Now everything is broken, but it's ok. We want to decide how many columns we want. But before that we need to know which is the height of each picture and then calculate how much space in the column it needs.
First, I'll create a new container for my images, a figure
is more than ok for this, and then I'll put something important, the size, for the span into the column grid-row-end
.
Second, I'll create a hook for ref each picture and calculate the size with the property getBoundingClientRect
...
const Figure = styled.figure`
margin: 0;
line-height: 0;
overflow: hidden;
grid-row-end: ${(props) =>
props.gridRowEnd ? `span ${props.gridRowEnd}` : 'auto'};
img {
width: 100%;
}
`;
const MansoryItem = ({ src }) => {
const size = React.useRef(null);
const [gridRowEnd, setGridRowEnd] = React.useState(null);
React.useEffect(() => {
if (size.current) {
const imageToLoad = new Image();
imageToLoad.src = src;
imageToLoad.onload = () => {
const { height } = size.current.getBoundingClientRect();
setGridRowEnd(Math.floor((height * 6.85) / 100) - 1);
};
imageToLoad.onerror = (error) => {
console.log({ error });
};
}
}, [size.current]);
return (
<Figure gridRowEnd={gridRowEnd}>
<img ref={size} src={src} alt="mansory-item" />
</Figure>
);
};
...
Show me on twitter what is your result @CarlosManotasV
There are a few things that we can improve like:
- The responsive layout
- What happends if any picture fails?
- Is it possible create this layout but with some pictures with a different width?.
But for this post I'm going to leave the result in this link here