1

I'm working on a photo album that loads several photos with different height and need to stack them at the top of the container, like this:

enter image description here

To do so, I settled the display property of the container to flex with align-items: flex-start and the children to inline-flex, like this:

HTML

  <div class="album1">
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <div>5</div>
    <div>6</div>
    <div>7</div>
    <div>8</div>
    <div>9</div>
  </div>

SCSS

  .album1 {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    div {
      display: inline-flex;
      width: calc(100% / 3);
      height: 100px;
      background-color: red;
      box-sizing: border-box;
      border: 1px solid black;
      justify-content: center;  
      align-items: center;
      &:nth-child(3n+2) {
        height: 200px;
      }
    }
  }

(Note that I set the height of the divs just for the example)

The behavior I got is this one.

enter image description here

I'm actually looking for a pure CSS solution, but I could go with a JS solution too.

Community
  • 1
  • 1
Carlos Pisarello
  • 1,254
  • 5
  • 20
  • 39
  • May i ask why don't you create a three column div instead of having 1 `
    `
    – boosted_duck Mar 29 '19 at 01:54
  • i actually made a workaround with that solution, but i have to control it with javascript (i'm working with vuejs) getting the pictures and making a function to push it to three different arrays to iterate on each column with v-for, i'm asking to know if there's a less complicated solution with css properties that i might not know. – Carlos Pisarello Mar 29 '19 at 01:57
  • @kukkuz yes, my bad, i will fix it on the post – Carlos Pisarello Mar 29 '19 at 02:40
  • @TemaniAfif this question is not a dynamic masonry layout :) – kukkuz Mar 29 '19 at 08:14
  • @kukkuz this question is a duplicate of the linked one. Masonry or not, the issue is the empty space that we need to remove that is already covered in the other question and using masonry is one way among alot. As a side note, here is the intended result from the duplicate: https://i.stack.imgur.com/d991Y.png which is the same as the image here. Also note that he said *several photos with different height* then *that I set the height of the divs just for the example* – Temani Afif Mar 29 '19 at 08:21
  • My thinking is that OP wants a simple layout as in the question, and not a very dynamic masonry style one... – kukkuz Mar 29 '19 at 08:36
  • @kukkuz in this case the OP should edit the question to remove *the different height* part which will make this a simple layout. If not, the question is still a duplicate but you already reopened the question... we should probably wait for more clarification. As as side note, a simple layout is a particular case of a complex one which also confirm the duplicate. – Temani Afif Mar 29 '19 at 08:54
  • as you said, we should always wait for taking the time to know what *exactly* the OP wants before closing it... anyway I don't want to argue about it :D – kukkuz Mar 29 '19 at 08:57
  • @kukkuz you should wait before reopening because you claimed it's as simple layout ;) I closed based on the actual content of the question where it's said we have *different height* thus a dynamic layout. – Temani Afif Mar 29 '19 at 10:33

6 Answers6

1

DISCLAIMER The solution here lies in the concept and the css implementation, it's irrelevant whether someone uses angular, jquery, vanilla js, or vue

I think what you wanna do is render the data in 3 separate columns if you want css to handle everything. You'll have to split the data into 3 almost equivalent arrays - I say almost because the number of images is not always going to be divisible by 3, so one or two arrays may have a missing or extra item. Once you have 3 arrays, you render the images in each array in a separate column instead of going across row by row. Here's a jsfiddle as an example.

I'm not familiar with vue so I used react as the library: https://jsfiddle.net/ahmadabdul3/sh8wjmtp/36/

if you look at the rendered output, the numbers still go across because of the way the array was split, but the rendering happens by column

and the code:

class TodoApp extends React.Component {
  state = {
    allItems: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
  };

  separatePhotosForColumns() {
    // split the items into 3 arrays
    // i'll just return 3 arrays for simplicity here
    return {
      one: [1, 4, 7, 10, 13, 16, 19],
      two: [2, 5, 8, 11, 14, 17, 20],
      three: [3, 6, 9, 12, 15, 18]
    };
  }

  render() {
    const parts = this.separatePhotosForColumns();
    return (
      <div className='album'>
        <Column data={parts.one} />
        <Column data={parts.two} />
        <Column data={parts.three} />
      </div>
    )
  }
}

class Column extends React.PureComponent {
  getRandomHeight() {
    return Math.floor(Math.random() * 200) + 100;
  }

  render() {
    const { data } = this.props;
    const images = data.map(i => (
      <div 
        className='photo' 
        key={i} 
        style={{ height: this.getRandomHeight() }}>
        { i }
      </div>
    ));
    return (
      <div className='column'>
        { images }
      </div>
    );
  }
}

and the css is very simple:

.album {
  display: flex;
}

.column {
  flex-grow: 1;
  flex-shrink: 1;
}

.photo {
  width: 100%;
  min-height: 100px;
  box-sizing: border-box;
  padding: 20px;
  border: 1px solid #ddd;
}
Abdul Ahmad
  • 9,673
  • 16
  • 64
  • 127
  • OP is working with vue not react. – boosted_duck Mar 29 '19 at 02:43
  • @noobprogrammer I said in my answer that I don't know Vue so I used react as an example - the solution here lies in the concept and the css implementation, the library that is used is irrelevant. I could do this with pure javascript too – Abdul Ahmad Mar 29 '19 at 02:45
  • Despite the fact that i work with Vue, this answer is still aplyable following the components rendering and iteration logic, that is the same between the two frameworks, so it's still useful. – Carlos Pisarello Mar 29 '19 at 02:47
0

I think that you should use three columns for each picture instead of one for all of them.

<div class="album1">
    <div class="column">
        <div class="shortelement">1</div>
        <div class="shortelement">2</div>
        <div class="shortelement">3</div>
    </div>
    <div class="column">
        <div class="longelement">4</div>
        <div class="longelement">5</div>
        <div class="longelement">6</div>
    </div>
    <div class="column">
        <div class="shortelement">7</div>
        <div class="shortelement">8</div>
        <div class="shortelement">9</div>
    </div>

</div>

Then in CSS, you could do

 .album1{
        display:flex;
        flex-direction: row;
    }
    .column{
        display:flex;
        flex-direction: column;
    }
    .shortelement{
        height: 200%;
    }
    .longelement{
        height: 50%;
    }

You can modify some of the aspects of the code to reduce the repetitiveness of the code. I hope this helps!

0

Javascript Solution

If you are setting elements 2,5,8 etc as 200px, a quick fix is to use negative margins on the elements 1,4,7,10... and 3,6,9,12... elements as a factor of half that height (100px) - see demo below.

[...document.querySelectorAll('.album1 > div')].filter(function(e, i) {
  // filter elements 1,4,7,10... and 3,6,9,12...
  return (i + 1) % 3 == 0 || (i + 1) % 3 == 1;
}).forEach(function(e, i) {
  // set negative margins
  e.style.marginTop = (Math.floor(i / 2) * -100) + 'px';
});
.album1 {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

.album1 div {
  display: inline-flex;
  width: calc(100% / 3);
  height: 100px;
  background-color: red;
  box-sizing: border-box;
  border: 1px solid black;
  justify-content: center;
  align-items: center;
}

.album1 div:nth-child(3n+2) {
  height: 200px;
}

.album1 div:nth-child(1),
.album1 div:nth-child(3) {
  margin-top: 0 !important; /* no margin for 1 & 3 */
}
<div class="album1">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>

You can make this dynamic by using CSS variables (and control the heights using a variable):

let margin = -getComputedStyle(document.body).getPropertyValue('--height').replace('px','');

[...document.querySelectorAll('.album1 > div')].filter(function(e, i) {
  // filter elements 1,4,7,10... and 3,6,9,12...
  return (i + 1) % 3 == 0 || (i + 1) % 3 == 1;
}).forEach(function(e, i) {
  // set negative margins
  e.style.marginTop = (Math.floor(i / 2) * margin) + 'px';
});
:root {
  --height: 100px;
}

.album1 {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
}

.album1 div {
  display: inline-flex;
  width: calc(100% / 3);
  height: var(--height);
  background-color: red;
  box-sizing: border-box;
  border: 1px solid black;
  justify-content: center;
  align-items: center;
}

.album1 div:nth-child(3n+2) {
  height: calc(var(--height) * 2);
}

.album1 div:nth-child(1),
.album1 div:nth-child(3) {
  margin-top: 0 !important; /* no margin for 1 & 3 */
}
<div class="album1">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>

Pure CSS Solution

If CSS Grid layout is an option, it'd be easier - you can set a 3-column layout using grid-template-columns: 1fr 1fr 1fr and set row height using grid-template-rows: 100px. Now the magic is done by grid-auto-flow: dense - see demo below:

:root {
  --height: 100px;
}

.album1 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr; /* 3 columns */
  grid-auto-rows: var(--height); /* set row height */
  grid-auto-flow: dense; /* fills in the spaces */
}

.album1 div:nth-child(3n+2) {
  grid-row: span 2; /* sets double height */
  grid-column: 2; /* place in second column */
}

.album1 div {
  display: inline-flex;
  background-color: red;
  box-sizing: border-box;
  border: 1px solid black;
  justify-content: center;
  align-items: center;
}
<div class="album1">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
  <div>7</div>
  <div>8</div>
  <div>9</div>
</div>
Community
  • 1
  • 1
kukkuz
  • 41,512
  • 6
  • 59
  • 95
0

Using CSS column-count will do that easily. You don't need flex-box for that. You just define how many columns you want the browser will automatically size the widths of each column to evenly fill up the container. You can read more about it here: W3 CSS column-count. The downside to this method is though that text and images can only be organized from top to bottom and then flow into each column in that same arrangement.

Here's a pen I made demonstrating what I'm talking about: https://codepen.io/MatthewRader/pen/JzgvOV

Here's what your css should look like:

  .album1 {
    column-count: 3;
    gap: 5px;

    div {
      width: 100%;
      height: 100px;
      background-color: red;
      box-sizing: border-box;
      border: 1px solid black;
      margin-bottom: 5px;

      &:nth-child(3n+2) {
        height: 200px;
      }
    }
  }

Matthew T Rader
  • 176
  • 1
  • 9
0

Check below example for responsive photo grid

HTML

<section id="photos">
  <img src="images/cat-1.jpg" alt="Cute cat">
  <img src="images/cat-2.jpg" alt="Serious cat">
  ...
</section>

CSS

#photos {
  /* Prevent vertical gaps */
  line-height: 0;

  -webkit-column-count: 5;
  -webkit-column-gap:   0px;
  -moz-column-count:    5;
  -moz-column-gap:      0px;
  column-count:         5;
  column-gap:           0px;  
}

#photos img {
  /* Just in case there are inline attributes */
  width: 100% !important;
  height: auto !important;
}

@media (max-width: 1200px) {
  #photos {
  -moz-column-count:    4;
  -webkit-column-count: 4;
  column-count:         4;
  }
}
@media (max-width: 1000px) {
  #photos {
  -moz-column-count:    3;
  -webkit-column-count: 3;
  column-count:         3;
  }
}
@media (max-width: 800px) {
  #photos {
  -moz-column-count:    2;
  -webkit-column-count: 2;
  column-count:         2;
  }
}
@media (max-width: 400px) {
  #photos {
  -moz-column-count:    1;
  -webkit-column-count: 1;
  column-count:         1;
  }
}

https://css-tricks.com/seamless-responsive-photo-grid/

https://codepen.io/team/css-tricks/pen/pvamyK!

SachinPatil4991
  • 774
  • 6
  • 13
0

so there are plenty of good answer here as there are multiple paths for you to achieve what you're looking for. This is another quick and simple css grid implementation

.album1 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  width: 80%;
  margin: 0 auto;
}

.album1 > div {
  background: #777;
  height: 100px;
  border: 1px solid #000;
  box-sizing: border-box;
}

.album1 > div:nth-child(3n + 2) {
  height: 200px;
}

css grid is really good for laying out random or layouts or layouts that have things placed in odd places or offset picture grids. personally I like to use grid to do the layout and flexbox for the columns inside of the grid