8

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

Dan
  • 367
  • 1
  • 5
  • 17
  • Add `vertical-align: bottom` to your `img` elements ([demo](https://jsfiddle.net/03whzbdu/) | [explanation](https://stackoverflow.com/a/31445364/3597276)) – Michael Benjamin Oct 22 '19 at 03:05
  • Your demo link still exhibits the initial problem... resize the window and you'll see the images mis align every so often – Dan Oct 22 '19 at 07:25
  • Okay. Maybe it's a different problem. I've re-opened the question. – Michael Benjamin Oct 22 '19 at 13:44
  • Also, I don't see the misalignment problem you're describing. At least not on Chrome. – Michael Benjamin Oct 22 '19 at 13:45
  • Please see screen shot added to the question (Chrome v77, Mac) – Dan Oct 22 '19 at 19:14
  • Using the demo I posted above, I cannot replicate the problem in Chrome or Firefox (on PC) or Safari (on iPad). Everything looks good. Sorry, I don't have Chrome on this iPad. – Michael Benjamin Oct 22 '19 at 19:23
  • It happens on Chrome, Firefox and Safari for me. Are you resizing the window? Thats how I'm getting it to happen. – Dan Oct 22 '19 at 20:08
  • https://imgur.com/dl8YSw5 – Michael Benjamin Oct 22 '19 at 20:39
  • Here is a screenshot from your video.. https://postimg.cc/kDN6PcVj – Dan Oct 23 '19 at 06:32
  • I recommend you first clean your code, you are mixing flex with float, you have too many unnecessary divs, repeated IDs, repeated inline style. Read SO guidelines to ask questions, one is to show the minimum code to reproduce your problem, that doesn't look like the minimum. I bet that if you clean your html and use flex properly (or even grid, looks like a better fit for you) you'll have better results. – arieljuod Oct 24 '19 at 04:02

3 Answers3

3

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>
wbq
  • 633
  • 4
  • 13
2

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.

ebessa
  • 140
  • 5
0

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.
tao
  • 82,996
  • 16
  • 114
  • 150