Using CSS Grid for an Image Gallery

So today a customer asked if it was possible to do something like a gallery. This gallery should show the images from a number of users that most recently entered a workflow item. Something like Google Image Search or a Facebook Album, with the photos all layed out evenly as  squares, some bigger than the other. Well, providing that there are enough users that have uploaded their photo, I decided that it should be possible. So here's the mock-up I did to give an impression of the layout and some of the challenges I encountered.

To get you an idea where this is going, this is the end result I had in mind:

At the bottom of this post you can find a link to the live demo and you can download the actual source code from that example too.

Challenge 1: Not enough photos

Putting the photos on the page should not be that difficult. Using an asp;Repeater that hold just one asp:Image would do the trick in the page. And then using GetEntities to get the data and from that construct the Download URL. Then use that as the ImageURL for the asp:image. Unfortunately in my test database I didn't have enough users with images to fill the gallery. So I had to fall back on the great service of unsplash.com.

Unsplash.com will give you random images in any format you want, simply by URL. So if you need an image that is 400px by 400px, you can use this URL:

https://source.unsplash.com/random/400x400

Now all you have to do is create a list of the number of images you need and assign the URL. Unfortunately, calling exactly the same size at the exact same moment will give you exact the same image. So to avoid that, there is a simple trick. Just make each the URL for each list item 1px higher or wider. The difference in doesn't matter that much anyway since you can overrule that from the CSS later on without much distortion.

So I created the list:

private List<string> images = new List<string>();

and added 14 items like this in the Page_Load:

images.Add("https://source.unsplash.com/random/401x400");

Challenges 2: The Grid design

Now having 14 images on the page (inside a div with the class gallery) I could do the layout of the gallery. And since it should be something like a grid, the best way to do that is using CSS grid. So for the class gallery I ended up with this.

display: grid;
grid-template-columns: repeat(5, 200px);
grid-template-rows: repeat(4, 200px);

So that's 5 columns, 4 rows. So there's room for 20 images, right? Well, no. Since I wanted to display 2 random images twice the size, those two would take up 8 cells in the grid. Leaving room for 12 more in the grid. Hence the list of 14.

Challenge 3: The images on the grid

Without any CSS the images on the grid will show up in their original size. But they should have the size of the grid cell. This is easily done by setting the width and height to 100%. But if a user would upload a rectangle image, the image might get pretty distorted. Luckily CSS provides us with an attribute object-fit which will take care of that. So the CSS for the image ended up as:

width: 100%;
height: 100%;
object-fit: cover;

Challenge 4: Enlarging 2 random images

So the grid is there and the images are nicely on the grid, now to enlarge two random images. Here's where a little Javascript comes in. Now getting the images inside the gallery can be done by using the getElementsByTagName function, followed by [x] where x is the number of the image element inside the gallery. So if you would want to enlarge the first image, you could use gallery.getElementsByTagName('img')[0]. Now just a little extra Javascript to create random numbers and we're done.

var gallery = document.getElementById('divGallery');

let x = Math.round(Math.floor(Math.random() * 7));
if (x == 8)
  x = 0;
if (x > 5)
  x = Math.round(x / 2);
let l1 = gallery.getElementsByTagName('span')[x];
l1.id = 'l1';

let y = Math.round(Math.floor(Math.random() * 7));
if (y > 6 && x < 5)
  y = x + x - 1;
if (y == x)
  y = x + 3;
let l2 = gallery.getElementsByTagName('span')[y];
l2.id = 'l2';

Well, probably not the most beautiful Javascript code I have ever written. But hey, it does the trick and it's just a mock-up. ;-)

Now by assigning 2 ID's to these 2 images, I could change their appearance on the grid in the CSS:

grid-column-end: span 2;
grid-row-end: span 2;

Now this works but has an unwanted side effect. because two random images take up more space in the grid, all the other items are shifting as well. This can result in some blank spaces on the grid, and some items that end up on a new row. But the grid should be filled evenly! So CSS attribute grid-auto-flow to the rescue. By adding this attribute to the gallery class, I can set it to dense which forces the last element back into the empty cells on the grid. And since the result of enlarging the images forces the last elements on a new row, they should be put back on the first available row. So all that can be done with this simple line in the gallery class:

grid-auto-flow: row dense;

Oh yeah, you can see that in this final script I target a span instead of an img. That's because I decided to add another challenge.

Challenge 5: Adding an overlay with mouse-over effect

So all these random images can make a pretty wild color combination. So I decided to add a dark-grey overlay over each image that would disappear on hover. Now an easy way to do that is use the pseudo-class ::before to create a virtual element with no content. Set the display to absolute and set top, right, bottom and left to 0 to make that cover the actual element.

Unfortunatelty, the pseudo-class ::before doesn't work on images. Or on any other HTML element that has no content like the BR and HR. So, instead of having just the asp:Image in my repeater, I added a span around it with the class tint:

<span class="tint"><asp:Image ID="img" runat="server" /></span>

With that, I could now style the tint itself:

position: relative;
float: left;
cursor: pointer;

then add the magic using the pseudo-class:

.tint:before {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(55, 55, 55, 0.3);
  transition: background 0.2s linear;
}

and finally use the pseudo-class hover to give it a mouse-over effect:

.tint:hover::before {
  background: none;
}

It's a wrap!

So there's the gallery. Once the customer is happy with the layout, all I need to do is to replace the manually populated list with a BusinessEntities list and modify the ItemDatabound accordingly. To see the result in action I have created a demo page on our website. You can also download the complete source code there as well. Or click Ctr+U on the demo page to see the HTML source code.