-
Notifications
You must be signed in to change notification settings - Fork 102
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pure CSS implementation of Google Photos / 500px image layout #6
Comments
@LeaVerou I won't make this without reading your book CSS Secrets. Thank you very much for writing that awesome book~ |
Glad you liked it! (please consider leaving a review on Amazon?) Great post btw. |
I‘ve brought it on amazon.CN and left my reviews just now~ I even wander is there a second edition of CSS Secret which can employ the trick in this post :) Thank you very much for reply! |
Hi @xieranmaya, |
@Rplus Wow!!!Awesome solution!!! Thank you for sharing~ |
I learn this trick (
|
Awesome! I love it! ❤️ |
Nice Work, Looks great! I look forward to using this on an upcoming project. |
Excellent work, @xieranmaya!!! |
@mhmli, ref: |
@Rplus 👌 |
Wow, this is the most thorough post I've ever seen about achieving a CSS layout. Thanks so much for writing it! |
I should start using |
@siriokun https://material.google.com/material-design/elevation-shadows.html has a good logic for layering elements. Applying a logic means that you'll run into less problems with multiple levels. |
You are welcome: ) |
@xieranmaya This is ace. Really nice research into hitting a layout style pretty close to what's used on Google Photos. Seems to work pretty well in modern browsers. Flexbox ftw. |
You might want to tag that 500px link as NSFW (due to the erotic images) 😅 |
@addyosmani Thanks for your compliment. There will be another post to express how to achieve Google Photos layout which multi date can lay on the same row with more tricks |
@simevidas It's a dynamic page which I didn't notice that at the time of writing. Good idea and I added a hint after the link~Thank you. |
Great! |
Great~~~ |
This is magic. Thank you so much for painstakingly writing this out, and then painstakingly translating it to English. 🥇 |
This is fantastic, thanks! |
Awesome, thanks! |
Sure, I think the syntax is like this: for (let img of imgs) {
$('section').append(
$('<div>').css({
'width': img.width*200/img.height + 'px'),
'flex-grow': img.width*200/img.height
}).append(
$('<img>').attr({'src': img.url, 'alt': ""})
).append(
$('<i>').css('padding-bottom', img.height/img.width*100 + '%')
)
);
} But remember to also add the css from this project, as only setting the width is not enough. |
img.width and img.height does not function so unfortunately. I do not get the real width and height of the picture. Thanks anyway for your help. |
@neoman666 Google returns https://davidwalsh.name/get-image-dimensions as the second result. |
@neoman666 The width and height of the full size images have to be computed by your back-end, either on-the-fly if you get the images from a third-party source or pre-compute and save them in your database at upload if the images are yours. It‘s obvious from my example that imgs is an array of objects returned by the api call that include the dimensions. So original dimensions are NOT computed by angular or jQuery. However if you want to get the width and height on the client side you can do this: var img = new Image();
img.onload = function() {
alert(this.width + 'x' + this.height);
}
img.src = 'http://www.google.com/intl/en_ALL/images/logo.gif'; Which in jQuery is this: $(document).ready(function() {
$("img").on('load', function() {
var height = $(this).height();
var width = $(this).width();
$(this).parent().css({
'width': width*200/height + 'px'),
'flex-grow': width*200/height
});
$(this).siblings('i').css('padding-bottom', height/width*100 + '%')
});
}); |
I have found a solution in this example. https://codepen.io/pasyuk/pen/vgQgyb Thanks to all for the help. |
Sorry guys. Unfortunately I have another question. How should the css look like if every picture should get a link? Something like that:
In the above example no images are displayed. I would need a last help again. Then it's done. |
so, there is a spell mistake... |
Great & Thanks! Go it to work nicely. Meanwhile I wrote a template for Lightroom CC which generates an image gallery from selected images. Perfect for viewing on mobile devices. Also integrated http://idangero.us/swiper/ to view them images a bit larger. |
This saved my head today, thanks a billion |
Thank you so much for this. @xieranmaya I don't see a license for the code here. Is there any? Is it ok if I adapt some of this for https://github.com/creativecommons/cccatalog-frontend? |
This is awesome! Tired of those buggy JS/jQuery libraries to do this. Hail flexbox! Thank you very much! |
get rid of hugo-shortcode-gallery use pure css gallery based on xieranmaya/blog#6 use lazy loader "lozad"
2021 and your blog post stil shine, tks for share your knowledge... |
Absolutely brilliant, thanks for this. It was a huge help developing a dynamic image gallery with varying image heights / widths. One question, is there a way to calculate the sizes image attribute at different screen sizes, since it's varying per the image ratio, I can't wrap my head around how you'd determine what the image size would be at varying screen sizes? |
I think use `vw` is an idea, note that this solution does not stick to one
unit, any proper unit can work.
Erik Olsen ***@***.***> 于 2021年10月15日周五 00:07写道:
… Absolutely brilliant, thanks for this. It was a huge help developing a
dynamic image gallery with varying image heights / widths.
One question, is there a way to calculate the sizes image attribute at
different screen sizes, since it's varying per the image ratio, I can't
wrap my head around how you'd determine what the image size would be at
varying screen sizes?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAW26G6JYZDXOGEC4JYGAFLUG355LANCNFSM4CNGHJMA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
This is so cool! Thank you so much for your time and effort. I couldn't figure this one out and it was causing me headaches! |
This is fantastic work! 🤩 I know I'm being savagely perfectionist here, but does anyone else notice that 1 pixel line of the Of course, I can just change the background color, and it'll be barely noticeable. It would be nice to get it just pixel perfect though, if that's doable? 🙂 |
Remove the purple background and nobody will notice xD |
Very impressive! |
discovered this in 2023... best and awesome. many thanks. |
Awesome post very helpful 👍 is there a way to define a minimum width for each image ? In case I want to ensure there is enough space to add hover extra informations ? |
this is great! however, one thing it's missing, that some JS libraries allow, is the ability to truncate the gallery and display only this is functionality that i needed but i don't think it can be done with pure css. if you want something similar, i think you'll need to introduce some javascript to calculate the height of the rows and then update the container to have the appropriate height + overflow. given that the parent container is a const getPhotoHeight = (photo) => {
const itemStyle = window.getComputedStyle(photo);
const margin = parseFloat(itemStyle.marginTop) + parseFloat(itemStyle.marginBottom);
return photo.offsetHeight + margin;
}
const countPhotosInRow = (photo) => {
const photos = Array.from(document.querySelector('ul.gallery').children);
const baseOffset = photo.offsetTop;
const startIndex = photos.findIndex(p => p.offsetTop === baseOffset);
const wrapIndex = photos.findIndex(p => p.offsetTop > baseOffset);
return (wrapIndex === -1) ? photos.length - startIndex : wrapIndex - startIndex;
}
const getContainerHeightForRows = (rows) => {
let totalHeight = 0;
let photoIndex = 1;
let photosInRow = 0;
let photo;
for (let i = 0; i < rows; i++) {
photo = document.querySelector(`ul.gallery li:nth-child(${photoIndex})`);
if (!photo) {
break;
}
totalHeight += getPhotoHeight(photo);
photosInRow = countPhotosInRow(photo);
photoIndex += photosInRow;
if (photoIndex >= props.photos.length) {
break;
}
}
return totalHeight;
}
const truncateHeight = (height) => {
const container = document.querySelector('ul.gallery');
container.style.overflow = 'hidden';
container.style.height = `${height}px`;
}
const maxRows = 2;
const height = getContainerHeightForRows(maxRows);
truncateHeight(height); the style of the container should also have an initial style of |
This is a very nice implementation. But one question strikes me. Since you don't know the image count per row, and those images are growing by the remaining space of each row, how would you choose the correct Sizes attribute together with sourceset to get truly responsive images? |
I've struggled with this as well - after trying several "weird tricks" (including some weird hacks with container queries) I concluded it's (still) not really possible with native web tech at this time. You need the rendered container sizes - you need them before the image actually loads. So you need JavaScript. Would love to hear if you come up with something that can do it natively. 🙂 |
This post is translated from Chinese, thanks to my sister Tian Qiong and my friend Tang Shuang for their hard work, and the original Chinese edition is here: #4
Pure CSS implementation of Google Photos / 500px image layout
For sharing the secrets of getting most perfect effect in every step to the end, this can be a very long post.
So you guys can jump to the part of code, demo and HOWs, which follow the last main title, if you don’t want to read it from the beginning. Or you can just check the source code of the bellow link.
But my suggestion is to read it from the beginning, because it will help you understand the HOWs better with the information provided in the former part, which mainly solves the problem of some corner cases.
First, let’s take a look at the result to make you more interested, resize or zoom the page to check the dynamic effect.
https://xieranmaya.github.io/images/cats/cats.html
Besides, to show the source code, jsbin.com is used in some demos that we will talk about later. But the jsbin.com sometimes may not work. If that happens, just rewrite the source code in the edit box without changing the meaning. For example, press the “enter” button in the last line.
PS: All the images used in the demo are from 500px.com, the the images' copyright are belonging to the author, the images' id are in the images' url and you can access the 500px original page with https://500px.com/photo/[id].
Ok, let’s start.
Firstly, let’s see the differences between three common images layout:
The above mentioned two layouts have onething in common is that the images are stretched in same proportion with the original image. That is to say, the images are just zoomed out and in without any change of the pictures' contents. This is the tendency of common picture web App now days. In fact, few websites will adopt the method of magnified or minified in unequal proportion(which is very ugly). The last method is to clip the image to square and put it in a square box, which can be achieved by the
background-size: cover;
CSS property.What's more, in the Pinterest layout, the display area will be smaller for the wider images. In the second layout, however, the display area will be smaller for the taller images.
In the second layout, because different rows have different height, the higher image can have a chance to show larger when it resides in a row which has larger row height.
In whole, the image layout leaded by "Google Photos" has better effect. About how to achieve the Google Photos / 500px algorithm is JS, readers can think about it by yourself, we'll talk about CSS implementation here.
After you think about the algorithm, you can open this link to see the dynamic demo of the layout in action, be sure to click the Layout button after ALL the images have loaded.
The demo showed the layout method: First lay all the images by a fixed height i.e. 200px, and then zoom in the images by row till they fill the parent's width ideally.
Next, let's summarize the standards of evaluating images layout based on the above analysis.
When I saw "Google Photos" image layout for the first time, I guess it is much probably JS involved. Because it's impossible justified on both sides under the condition of the same picture height, and in the same separation distance between the different pictures.(If each picture have the different separation distance while it can be justified on the both sides, we can use
inline
picture and text-justify to achieve this. And it's a choice when the images are small). After my observation, every row have different height, so I am sure there must be JS involved.But as a frontend developer, I perfer CSS to JS. However, when I saw more and mroe websites starts to use this layout, an idea occours to me: whether we can achieve this layout only by CSS instead of JS, especially not recalculate the layout in the process of resizing/zoom.
After some trial, I found a way to achieve similar layout only by CSS in some extent.(Only use CSS to achieve layout, what I mentioned here, referring to the subsequent steps of resizing and zooming can be stable without JS involved after rendering completed. That's to say, initial rendering can be achieved on the server, and the whole process is not involved JS. So it's natural to say that we only use CSS to achieve this layout).
Implementation process
Next, I will introduce the the entire attempt I tried step by step to achieve this layout with pure CSS.
First of all, let's set the same height for all the images
This method can't fill the image very appropriately in the horizontal space. The
flex-grow
property cames to my mind, it can make images' box to grow up to fill in the container's extra horizontal space. Naturally, the whole layout become flex.Set the
flex-wrap
of flex container intowrap
. In this way, line wrap will happens if one line can not lay one more image. Every line's images will fill in the container's horinontal space due to the grow. It seems that it's almost achieved our expectation effect, but every image has been stretched in non-proportion so that the images are out of shape. This is easy for us, we can fix it withobject-fit: cover;
. However, part of the image will be clipped.Final demo: http://jsbin.com/tisaluy/1/edit?html,css,js,output
Actually, the above mentioned DOM structure can't be used in the practice
object-fit
support, so the picture will be distorted. The reason is the images has no container, and it's not posible to usebackground-size
to solve this problem.*By using the object-fit porperty in supported browser, part of images will be cliped( these two rules have been mentioned before).
Thus, the above mentioned layout can't be used in any kind of production situation.
Add container for the
img
tagNext, we change the DOM structure to this:
We added a container for each image. Firstly, the image height is set 200px still, and every
div
will be stretched by its image child node. Next, if we set aflex-grow: 1
for thediv
s, extra spaces in each row will be divided equally by eachdiv
. Then thediv
will be wider, but the image width can't fill in the div completely, it's obvious.Then, if we set the image width to 100%, the growed space will not be redivid by
div
in IE and FF.(I personally think this is a very interesting , the image stretchs div first, then thediv
's grow stretchs the image). But in Chrome, setting 100% width for image will cause the extra space in each row to reallocate. Which in result makes every container's width to more closer. However, it’s not what we want.After several combinations of css properties, I almost did it. If we set the image's
min-width
andmax-width
to 100% at the same time, the display effect in Chrome will be the same with IE and FF. Lastly, we set the image'sobject-fit
property tocover
, and the image will fill in the container with equal proportional stretch.This method still is not perfect just like the former one. The images height is all same, and partial image has been clip for obvious reason.
The entire demo of the above solution: http://jsbin.com/tisaluy/2/edit?html,css,output
If the image height value is set to a small value, the above layout is roughly right. There will be more images in every row, so extra space in each row will be less and divided by more images. In this way, the ratio of every container will be more close to the real image ratio. Most of images can display the main content.
The nasty last row
The only problem occurs in the last row. When the last row only has few images, it will fill in the whole line due to
flex-grow
. However, our height is set to 200px, so the display area will show less images content. The situation will be worse when the image is higher and the display area(which is the container) is wider.The solution for this situation is to let the last few images to not grow. We can calculate the average images in every row in case of distorting terribly.
And media query is adopted here when the screen have different width, the number of elemnets in the last row will change according to the window's width:
Every certain width have to be many “nth-last-child” selectors so that above code is rather complicated. We can use preprocessor to loop out this code, but there are still many repetitious code generated.
Is there any way to just appoint how many elements instead of several “nth-last-child” selectors? The answer is yes. Here we should mention the “~” operator in CSS. Let’s write:
First, we can select the eighth last element. Then the following node after the eighth last element can be selected by the
~
combinator. Finally, the last eight elements are selected successfully. Furthermore, we can rewrite the selector to “div:nth-last-child(9) ~ div”, and we may select the last eight elements only with one selector.The real effect varies from the different selectors we chose in the above rear elements:
Actually, the way of choosing last several images is not perfect, because it's not guaranteed that the flex items you select is in the last row. It might be only one image in the last row, and if this happens, first images in the next-to-last row will grow badly. (Because the last N images won’t grow). Or the last images in the last two rows are not up to a certain amount, and then the next-to-last row won’t grow to fill in this row. All may lead to the format disorder.
Is there any way to prevent just the last row's elements' growing? I thought for a long while, even to seek for a
last-line
orpseudo-class
, but I failed. And then one day, I got the answer occasionally:Adding another element to the last element as the last flex item, and sets its
flex-grow
to a extremely large value (e.g., 999999999). Then the remaining space in the last line will be taken fully up by this extra element's grow. The other elements are thus not growed, what’s more, we can achieve this by pseudo elements(But IE browser does not support flex properties on pseudo elements, so it is essential to adopt a real element placeholder):Ok, we basically solve all the problems encountered in this layout.
Demo: resize or zoom first, then observe the images in the last row: http://jsbin.com/tisaluy/3/edit?html,css,output
But there is one last question, this layout is just like the former one except for it has a container which can show some extra info for the images. If you load the layout pages online, pages will occur a severe blinking issue. Before downloading the images, we have no ideas about the width and height. It’s unpractical to wait images loading completed to stretch the container. What’s worse, we need refresh pages more than ten thousand times at develop which will have a very worse effect on our developer's eyes too.
So, we have to render the images display area(i.e. its container) in advance. (Actually, almost all websites with images adopt this approche.) JS have to be applied in here. Those works can be finished on the server or by any template engines. (Below codes employ template syntax in Angular.)
Once this layout finished, all the subsequent actions(resize,zoom) won’t disorder the layout without JS involved. It did it successfully with CSS.
We achieve the images layout we expected.
Demo, notice the expression in the template: http://jsbin.com/tisaluy/9/edit?html,css,output
So, what about the final effect of the layout?
Acturally I wrote code to calculate the percentage of each image shows. When images height is set to 150px initially, one third images can show 99% content. The worst one or two case of images can display about 70% content. In average, the display percentage of all images is about 90%. The display effect will be better if the image is shorter(less height), and the display effect will be worse if the image is higher(larger height).
I gave up this solution later, so there is no need to present the detailed demo.
You may thought you are played fool of if you don't keep reading. Because it didn’t accomplish the Google Photos images layout as you expected.
Because of the same height in each row, most images are not displayed completely. And this layout are totally unrelated to the marvelous Google Photos / 500px layout.
The real post starts from here, below approche came after the above method after a long while. The above content just introduce the solution for some corner cases.
We can see that the above solution doesn't make every image to display completely. If you need to accomplish 500px layout, the height of each row is most likely to not equal to each other in that layout.
For the start, I guessed CSS can dealt with nothing more than the above extent.
However, what I needed later proved me wrong:
I want to display some content in a square container, and I want the square container to always spread with the window without spare room (except the blank between the elements) regardless of the browser window’s width. At first glance, this may require the JS participation: read the browser window's width first, then calculate the size of a square container, and then render.
Open this demo to see the effect, try to resize or zoom the page: http://jsbin.com/tomipun/4/edit?html,css,output
During the process of resize / zoom the page, the square container will grow or shrink in real-time but will always be square, and its size is remaining in a specific range. It will grow to a point, later it wanes. If we only focus on one demo, it’s hard to come up with the solution to achieve the fixed aspect ratios. But if there is a square container and its side length is the half of the browser width, you guys might know the solution.
As we know, if we specify margin or padding value by percentage, the value is relative to it's parent element's width. Namely, if we give a block element 100% for its
padding-bottom
and itsheight
setting to 0, the element will keep to square and it will change with the container height. If we want to change the size of the square, we only need to change the width of the parent container, the height will change appropriately.Check this link:
http://jsbin.com/lixece/1/edit?html,css,output
the colored block will grow or shrink but will remains square while we resize the window. If we take browser window as a reference, this effect can be achieved with vw / vh in modern browser. If not, we can choose vertical padding to accomplish.
Then it occurs to me, if we do not set flex item's height and let it stretch by its child element, and the child element's width is 100% and padding-bottom is 100%, then both flex item the child element will keep square at the same time. As a result, the above square grid layout can be finished.
This is far away from the perfect result. If the element number in the last row differs from the number of the previous rows, color block in the last row will be larger due to growing. How to make the last row’s elements stay the same with the previous row? It doesn’t work if we use a extra element and set a large
flex-grow
value to fill the spare room in the last row. We need keep the last row's element to grow same space with the previous rows.To be honest, the solution is simple. We just treat the last row to be not the last row, we treat the second last row as last row!
We can add some placeholder elements to let the last elements to be in the visual last row. And the placeholder elements occupied the real last row. Then change the placeholder height to 0. How many placeholders we need? The number should stay the same with the posible maximum of elements per row. Such as, the above demo has 8 extra placeholders which you can check in the source code. For better semantic meaning, we can give them special class name to act as placeholders.
In this way, the square layout which fills in the horizontal width can be accomplished(which is the previous demo,and you can check the source code or inspect to see the placeholder elements).
Although the most advanced flexbox layout is adopted, CSS can’t accomplish the perfect layout without cropping. So, I thought the essay might stop here.
- FAKE EOF -
But when I woke up on the morning of April 2,an idea came into my mind that since you can always keep the container to square, would it mean that you can keep it in any proportion? Certainly yes, we only have to set the padding-bottom of the child element which stretching its parent element a value we want! In this way, maybe the layout can be achieved only by CSS!
Certainly, as I mentioned before, because of the slow loading of images, this layouts should often know the width and height of the image in advance to pre-render the container, and directly put the image into it after the image is loaded or loading.
So we still need JS or server to calculate the images' aspect ratio, and set it in the padding-bottom to ensure that the aspect ratio of the container is always the same to its internal images.
Firstly, we display all the images in the height of 200px, as shown below:
In the above layout, because of the flex-wrap, every row will be breaked and extra spare room will be left if there are too many images in one row. The aspect ratio of each container will always stays the same with the images which we will put into it later.
For demonstration purpose,I will set the size of the image a quarter of the container, it should be clear that the bottom right corner of the image is at the center of the container.
Demo: http://jsbin.com/tisaluy/5/edit?html,css,output
Next, we need to make all the elements to grow. Could we set all their
flex-grow
to 1?In fact, if we tried, we will know 1 is not right. Because we need every container can keep its proportion when it grows.
Demo:http://jsbin.com/tisaluy/6/edit?html,css,output
If we set the flex-grow of flex item to 1, the container proportion is different from the images then. In this demo I set the images height to the container’s height for better observation.
Through some simple calculations, we will find that the width of each image(which is also the container) in the horizontal direction is just the percentage that its with from the total with of all images in this row.
In the case that it were not growed, the container width of each picture would been prorated. The remaining space of each line, we also want it to distribute in the current proportion of the width of the container, so the value of each containers flex-grow, is just its width, but not with the
px
unit.The final code are shwon as:
As a result, the current line will be filled with the container, and the same aspect ratio will be kept as the internal image which will put into the it:
Demo: http://jsbin.com/tisaluy/8/edit?html,css,output
As for how to deal with the last line, you can use a great element of flex-grow to fill the remaining space just as I described above.
After rendering the layout, you can feel free to resize and zoom, even without JS, the layout will not be in disorder.
Here, we finally achieve an image layout similar to Google Photos / 500px .
Summarize of the principles of this solution:
Advantages of this layout:
Finally, we will talk about some of the shortcomings of this solution:
The graceful degradation
Since flexbox is not supported in IE9 and below, graceful degradation of this layout is necessary. It should not be too bad to display these pictures in square on a browser which lack the flexbox support. Then float or inline-block can be used to break the line which it is not detailed here.
Finally, float the layout by day will create a layout like Google Photos which images can display in one row if images of consecutive days are few. Again, I came up with another method to address the Google Photos like layout which I'll write it in the next post.
That's all, folks! If you have any thoughts or questions, feel free to leave a comment!
The text was updated successfully, but these errors were encountered: