8

Given a list of thumbnails in a flex layout, is it possible to target the first and the last element per row using only CSS if the flex container has variable width?

.thumbnails {
  display: flex;
  margin: 0;
  padding: 0;
  list-style-type: none;
  flex-flow: row wrap;
}
.thumbnail {
  width: 250px;
  height: 250px;
  margin: 5px;
  background: #ccc;
  border: 1px solid #000;
}
<ul class="thumbnails">
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
</ul>
Michael Benjamin
  • 346,931
  • 104
  • 581
  • 701
  • Hard to say without any code to go on. – Drew Kennedy Aug 15 '16 at 20:38
  • Well, this is a theoretical question, I will set-up a fiddle if that's really needed, even though it's just a `ul` with `li`s. –  Aug 15 '16 at 20:39
  • Are the rows even or uneven? If there's a consistent number of items per row, you can use `:nth-child` – zzzzBov Aug 15 '16 at 20:39
  • Here's a fiddle: https://jsfiddle.net/Lmc2e6wt/1/ The container has a variable width so the wider it gets, the more elements fit inside a row. What I need is to target the first and last element per row. –  Aug 15 '16 at 20:42
  • Is the number of rows fixed? – andreas Aug 15 '16 at 20:43
  • Nope, it's unknown. –  Aug 15 '16 at 20:44
  • Ah sorry, I meant the number of columns... – andreas Aug 15 '16 at 20:45
  • Again, no, it depends on the width of the flex container. In my fiddle, if you resize the output area on the bottom right, you'll see how the squares reorganize themselves. –  Aug 15 '16 at 20:46
  • I want to say this isn't possible, but `flex` has surprised me before. You might be stuck using media queries. – Drew Kennedy Aug 15 '16 at 20:48
  • 1
    Such targeting is not natively possible in CSS. If you use media queries, and the items are fixed width, you can target first and last elements in a row based on the ranges you set. More details and examples here: http://stackoverflow.com/a/32811002/3597276 – Michael Benjamin Aug 15 '16 at 20:48
  • This message talks about a possible solution, but I don't think it ever went anywhere: https://lists.w3.org/Archives/Public/www-style/2015Mar/0527.html – Dark Falcon Aug 15 '16 at 20:51

3 Answers3

2

Flexbox are amazing, but sometimes hard to deal with..

Here you have an updated version of JQuery script to target the first-child and last-child of each row using a flex layout :

// Call the function anytime needed, by default on loading and resizing window, see below
function flexboxRowLastChild(){

    $(document).ready(function(){
    
        //For each element with class="flexbox-wrapper"
        $('.flexbox-wrapper').each(function() {
        
            //Reset at every function call
            $(this).children('.flexbox-row-first-child').removeClass('flexbox-row-first-child');
            $(this).children('.flexbox-row-last-child').removeClass('flexbox-row-last-child');
            
            //Set :first-child and :last-child (or use css pseudo-element instead)
            $(this).children().eq(0).addClass('flexbox-row-first-child');
            $(this).children().eq($(this).children().length - 1).addClass('flexbox-row-last-child');
            
            var rowWidth = $(this).children().eq(0).outerWidth(true);
            
            //For counting number of row if needed
            var nbrRow = 1;
            
            for (var i = 0; i < $(this).children().length; i++) {
                if (rowWidth <= $(this).width()) {
                //Sum of every children width (with margin) while it's less than the flexbox-wrapper width 
                    var rowWidth = rowWidth + $(this).children().eq(i+1).outerWidth(true);
                } else {
                //Set the flexbox-row-first-child and flexbox-row-last-child classes and begin to check for a new row
                    $(this).children().eq(i-1).addClass('flexbox-row-last-child');
                    $(this).children().eq(i).addClass('flexbox-row-first-child');
                    var nbrRow = nbrRow + 1;
                    var rowWidth = $(this).children().eq(i).outerWidth(true) + $(this).children().eq(i+1).outerWidth(true);
                }
                
            }
            
        });
        
    });

}

$(document).ready(function(){

    // Call the function on window load
    flexboxRowLastChild();

    // Call the function on window resize
    $(window).resize(function(){
        flexboxRowLastChild();
    });

});
.flexbox-wrapper {
  display: flex;
  margin: 0;
  padding: 0;
  list-style-type: none;
  flex-flow: row wrap;
}
.thumbnail {
  width: 100px;
  height: 100px;
  margin: 5px;
  background: #ccc;
  border: 1px solid #000;
}

.thumbnail.flexbox-row-first-child {
  background: #000;
}

.thumbnail.flexbox-row-last-child {
  background: #444;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
<ul class="flexbox-wrapper">
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
</ul>
Lyddark
  • 61
  • 2
  • This seems to work perfectly for a use-case of mine in a responsive project. The wrapper, and the first-item and last-item elements could well be defined as variables, to make the script easier to reuse. Thanks a lot. – physalis Apr 29 '23 at 16:35
1

I would set a width for the flex elements (like 25% to display 4 in a row or 33.333% to display 3). This way you can always tell how many items per row. In a row of 3 you can target the last element using ... element:nth-child(3n) and the first element by using element(3n-2). Same applies for any number of items per row. You might get something weird looking last row if contains less than the number you thought. For example if you set your flex elements with 33.333% width and the last row only contains 2 elements the style mayy broke depending on how you justify the flexbox. You can always push the last element to the left by applying margin-right: auto.

0

You could get this done using JavaScript like i did here:

var wrapper = document.getElementById('wrapper');
var thumbnails = document.getElementsByClassName('thumbnail');

var thumbnailsPerLine = Math.floor(wrapper.offsetWidth / 262); // 262px = complete width of thumbnail including margin and border.

for(var i = 0; i < thumbnails.length; i++){
  // add 'first-class' to first element.
  if(i === 0) thumbnails[i].classList.add('first');
  
  // check if its the last of the row, then add 'last-class'
  if((i+1) % thumbnailsPerLine === 0) thumbnails[i].classList.add('last');
  
  // check if its the first of a new row, then add 'first-class'
  if(i % thumbnailsPerLine === 0) thumbnails[i].classList.add('first');
  
  // check if its the last thumbail of all and check if its not the first of its row, then add 'last-class'
  if((i+1 === thumbnails.length) && !thumbnails[i].classList.contains('first')) thumbnails[i].classList.add('last');
}
.thumbnails {
  display: flex;
  margin: 0;
  padding: 0;
  list-style-type: none;
  flex-flow: row wrap;
}
.thumbnail {
  width: 250px;
  height: 250px;
  margin: 5px;
  background: #ccc;
  border: 1px solid #000;
}
.first {
  background: #000;
}
.last {
  background: #444;
}
<ul class="thumbnails" id="wrapper">
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
  <li class="thumbnail"></li>
</ul>
jsadev.net
  • 2,800
  • 1
  • 16
  • 28