Aligning image height along a row

Viewed 439

Struggling to get things to behave.

I have a dynamic list of images on a responsive site. The layout is generated where images can be in rows/columns or columns rows.

This example is close but the paired images don't align at the bottom as the browser resizes...

<div style="width:100%;">
    <div style="width:60%; display: flex;  margin-left: auto;  margin-right: auto;">
        <div id="outterrow" style="width:100%;  float:left; display: flex; padding-bottom: 1.15%; ">
            <div id="column" style="float: left;overflow: hidden;background-color: inherit;width: 49.35%;">
                <div id="row" style=" padding-right: 2.330294%; "><img title="2.jpeg" src="https://i.postimg.cc/Xv5YsYv7/2.jpg" sizes="100vw" width="100%"> 
                </div>
            </div>
            <div id="column" style="float: left;overflow: hidden;background-color: inherit;width: 50.65%;">
                <div id="row" style=" "><img title="1.jpg" src="https://i.postimg.cc/B6cQG7Dr/1.jpg" sizes="100vw" width="100%"> </div>
            </div>
        </div>
    </div>

    <div style="width:60%; display: flex;  margin-left: auto;  margin-right: auto;">

        <div id="outterrow" style="width:100%;  float:left; display: flex; padding-bottom: 1.15%; ">
            <div id="column" style="float: left;overflow: hidden;background-color: inherit;width: 100%;">
                <div id="row" style=" "><img title="3.jpg" src="https://i.postimg.cc/ZnbYYPxC/3.jpg"  sizes="100vw" width="100%"> </div>
            </div>
        </div>
    </div>
    
    <div style="width:60%; display: flex;  margin-left: auto;  margin-right: auto;">

        <div id="outterrow" style="width:100%;  float:left; display: flex; padding-bottom: 1.15%; ">
            <div id="column" style="float: left;overflow: hidden;background-color: inherit;width: 43.55%;">
                <div id="row" style=" padding-right: 2.640643%; "><img title="5.jpg" src="https://i.postimg.cc/bwsJ2Tcn/5.jpg"  sizes="100vw"  width="100%"> </div>
            </div>
            <div id="column" style="float: left;overflow: hidden;background-color: inherit;width: 56.45%;">
                <div id="row" style=" "><img title="4.jpg" src="https://i.postimg.cc/XJ07m6ZK/4.jpg" sizes="100vw" width="100%"> </div>
            </div>
        </div>
    </div>
    
</div>

I've experimented with object-fit but Safari seems to fall apart.

EDIT: for reference here is an example of the problem.

enter image description here

3 Answers

You could of course use background images instead of the HTML img-tag. If you can access the image height I would recommend to fill that one in dynamically via JavaScript or PHP (or whatever setup you have). I placed a spacer-div inside the wrapping container, which could be used for that purpose.

#wrap_all {
  width:100%;
}

#inner {
  max-width:1210px;
  padding:0 50px;
  margin:0 auto;
}

.flexrow {
  display:flex;
  justify-content: space-between;
  background:#f5f5f5;
  flex-wrap:wrap;
}


.flexcol {
  flex:0 0 49%;
  background-repeat: no-repeat;
  background-size: cover;
  background-position:top center;
  margin-bottom: 2%;
}

.spacer.height-80vh {
  height:80vh;
}

.flexcol.fullwidth {
  flex:0 0 100%;
}
<div id="wrap_all">
    
  <div id="inner">
    
    <div class="flexrow">
      
      <div class="flexcol" style="background-image: url('https://i.postimg.cc/Xv5YsYv7/2.jpg')">
        <div class="spacer height-80vh"></div>
      </div>
      
      <div class="flexcol" style="background-image: url('https://i.postimg.cc/B6cQG7Dr/1.jpg')">
        <div class="spacer height-80vh"></div>
      </div>
      
      <div class="flexcol fullwidth" style="background-image: url('https://i.postimg.cc/ZnbYYPxC/3.jpg')">
        <div class="spacer height-80vh"></div>
      </div>
      
      <div class="flexcol" style="background-image: url('https://i.postimg.cc/bwsJ2Tcn/5.jpg')">
        <div class="spacer height-80vh"></div>
      </div>
      
      <div class="flexcol" style="background-image: url('https://i.postimg.cc/XJ07m6ZK/4.jpg')">
        <div class="spacer height-80vh"></div>
      </div>
      
    </div>
    
  </div>  
  
</div>

This is called pixel rounding, or the sub-pixel problem, and it's a basic math issue:

You have two containers with a relative width of ~50% sitting inside of a responsive parent. What should happen when that parent element's width is an odd number? Say 211 pixels. 50% of 211px is 105.5px. BUT, there is no such a thing as .5px - Your screen just can't turn on only half of that little light bulb that physically defines a pixel. The browser rounds it to a larger or smaller number.

If you look close, the layout is composed by:

  • Columns with decimal % values for width (49.35%)
  • Decimal % for spacing (padding: 2.330294%)
  • Images with random widths and heights
  • No defined height for any of the columns.
  • All wrapped by a responsive div, which means no absolute width or height anywhere.

There's just no solution to insert images of varying dimensions inside of divs of varying dimensions inside of a % oriented layout. That is just too much pixel rounding to account for. If you look close, this layout is "broken" 100% of the time... it's either leaving blank pixels or shrinking/distorting the images inside.

Whatever hack anybody present will either generate that little blank pixel, distort the images (again, they don't have the same height or width) or secretly hide pixels from the image somewhere.

Now, a quick hack for the specific snipet you've presented:

  • use absolute px to padding-left
  • add a blank spacer to padding-bottom using :after

img {
  vertical-align: bottom;
}

.column {
  position: relative;
}

.column:after {
  content: '';
  display: block;
  position: absolute;
  bottom: 0;
  width: 100%;
  background: #fff;
  height: 6px; /* this will fake padding bottom */
}

.row {
  padding-left: 6px;
}
<div style="width:100%;">
  <div style="width:60%; display: flex;  margin-left: auto;  margin-right: auto;">
    <div id="outterrow" style="width:100%;  float:left; display: flex;">
      <div class="column" style="float: left;overflow: hidden;background-color: inherit;width: 49.35%;">
        <div class="row" ><img title="2.jpeg" src="https://i.postimg.cc/Xv5YsYv7/2.jpg" sizes="100vw" width="100%">
        </div>
      </div>
      <div class="column" style="float: left;overflow: hidden;background-color: inherit;width: 50.65%;">
        <div class="row" style=" "><img title="1.jpg" src="https://i.postimg.cc/B6cQG7Dr/1.jpg" sizes="100vw" width="100%"> </div>
      </div>
    </div>
  </div>

  <div style="width:60%; display: flex;  margin-left: auto;  margin-right: auto;">

    <div id="outterrow" style="width:100%;  float:left; display: flex;">
      <div class="column" style="float: left;overflow: hidden;background-color: inherit;width: 100%;">
        <div class="row" style=" "><img title="3.jpg" src="https://i.postimg.cc/ZnbYYPxC/3.jpg" sizes="100vw" width="100%"> </div>
      </div>
    </div>
  </div>

  <div style="width:60%; display: flex;  margin-left: auto;  margin-right: auto;">

    <div id="outterrow" style="width:100%;  float:left; display: flex; ">
      <div class="column" style="float: left;overflow: hidden;background-color: inherit;width: 43.55%;">
        <div class="row"><img title="5.jpg" src="https://i.postimg.cc/bwsJ2Tcn/5.jpg" sizes="100vw" width="100%"> </div>
      </div>
      <div class="column" style="float: left;overflow: hidden;background-color: inherit;width: 56.45%;">
        <div class="row" style=" "><img title="4.jpg" src="https://i.postimg.cc/XJ07m6ZK/4.jpg" sizes="100vw" width="100%"> </div>
      </div>
    </div>
  </div>

</div>

Again, no "solution" exists under these conditions, just hacks with drawbacks.

*To be fair with browsers, they are doing such a good job handling pixel rounding that most developers never even realize this is an issue.

When CSS fails short, I go vanillaz:

document.addEventListener('DOMContentLoaded', function() {
  [...document.querySelectorAll('.flex-fill')].forEach(div => {
    [...div.querySelectorAll('img')].forEach(img => {
      img.addEventListener('load', () => {
        const box = document.createElement('div');
        const basis = img.height ? img.width * 100 / img.height : 0;
        box.style.background = `url('${img.src}') center /cover no-repeat`;
        box.style.flexBasis = `${basis}%`;
        box.setAttribute('data-basis', basis);
        div.insertBefore(box, img);
        box.appendChild(img);
      })
    })
  });
  const resizeFlexFills = _.throttle(() => {
    [...document.querySelectorAll('.flex-fill')].forEach(div => {
      const gutter = (div.currentStyle || window.getComputedStyle(div))
        .marginBottom.match(/\d+/)[0] || 0;
      const children = [...div.querySelectorAll('div')];
      const basisSum = children.map(e => e.dataset.basis)
        .reduce((a, b) => Number(a) + Number(b));
      div.style.height = `${(div.offsetWidth - ((children.length - 1) * gutter)) * 100 / basisSum}px`;
      [...div.querySelectorAll('div:not(:last-child)')].forEach(div => {
        div.style.marginRight = `${gutter}px`
      })
    })
  }, 100, {
    leading: true,
    trailing: true
  });
  window.addEventListener('load', resizeFlexFills);
  window.addEventListener('resize', resizeFlexFills);
});
.flex-fill {
  width: 100%;
  display: flex;
  height: 0;
  transition: height .3s cubic-bezier(.4, 0, .2, 1);
  /* gutter size (remove if you don't want any space between them) */
  margin-bottom: 8px;
}

.flex-fill img {
  display: none;
}

@media(max-width: 600px) {
  .flex-fill {
    height: auto !important;
    display: block;
  }
  .flex-fill img {
    display: block;
    width: 100%;
   /* needs to be same as gutter size, above */
    margin-bottom: 8px;
  }
  .flex-fill div {
    margin-right: 0 !important;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

<div class="flex-fill">
  <img title="1.jpg" src="https://i.postimg.cc/B6cQG7Dr/1.jpg">
  <img title="5.jpg" src="https://i.postimg.cc/bwsJ2Tcn/5.jpg">
  <img title="4.jpg" src="https://i.postimg.cc/XJ07m6ZK/4.jpg">
  <img title="2.jpeg" src="https://i.postimg.cc/Xv5YsYv7/2.jpg">
  <img title="3.jpg" src="https://i.postimg.cc/ZnbYYPxC/3.jpg">
</div>
<div class="flex-fill">
  <img title="2.jpeg" src="https://i.postimg.cc/Xv5YsYv7/2.jpg">
  <img title="1.jpg" src="https://i.postimg.cc/B6cQG7Dr/1.jpg">
</div>
<div class="flex-fill">
  <img title="3.jpg" src="https://i.postimg.cc/ZnbYYPxC/3.jpg">
</div>
<div class="flex-fill">
  <img title="5.jpg" src="https://i.postimg.cc/bwsJ2Tcn/5.jpg">
  <img title="4.jpg" src="https://i.postimg.cc/XJ07m6ZK/4.jpg">
</div>

Notes:

  • what's special about it is that the image ratios are kept, with or without gutters and, as you can see, it's pixel perfect
  • for that to happen, however, resizeFlexFills() needs to be bound on load and resize to update the heights
  • therefore I used lodash's throttle to only allow running it every 100ms;
  • the gutter is set via CSS and only works with px. It can be done smarter, but parsing CSS units into pixels is way out of this question's scope (IMHO).
  • the transition is totally ditch-able
  • it's not "responsive" but could easily be done so. for that to happen i'd leave the <img>s in, display them as blocks under desired device width, while changing the flex-direction to column.
    actually, I made it responsive. If you don't want it, remove the @media query code.
Related