4

I've seen fixes for individual parts of this problem but not one for a collective solution. I can solve everything without resorting to javascript formatting except for margins from elements being carried over to the top of the next column (I can't understand why it hasn't been fixed but it appears to be a bug in css3 for some time that's impeding compositing layouts 1, 2)

I've a responsive div container broken into three columns (though this can change depending on the width of the page, per responsive layout), containg divs with a varying number of nested images of varying aspect ratios that each have a margin-bottom property. The above problem is very apparent so I'm looking for a solution to this.

Typically the suggestion involves the use of a column-break-inside: avoid; property alongside switching my margin-bottom to padding-bottom. This hack has seen some success with others and this is where I point you to the subject of my question. I cannot implement this as I'm using a seperate hack to prevent reflow of images that are lazy loaded (using the lazy sizes plugin*) into the columns (the padding-bottom as a ratio hack, 3).

So if I use padding-bottom to ensure that my column elements align without orphaned margins, I lose the ability to correct the reflow from lazy loading the elements into the columns. I can't use fixed sized elements as the column layout is responsive and the elements shrink and enlarge dynamically with the column size.

Is there anybody who has succeeded in solving both issues simultaneously without javascript formatting?

I'm keen to stick to this particular lazy loading plugin for reasons outside of the scope of this problem.

HTML Code:

<div id='columncontainer'>
    <div class='imagecontainer' style='padding-bottom:reflowPaddingAmountFromPHPvar;'>
        <img class='lazyload'>
        <div class='imagetextcontainer'>
            <div class='vertaligncontainer'>
                <p class='imagename'>Text</p>
                <p class='imagedesc'>Text</p>
            </div>
        </div>
    </div>
</div>

CSS Code:

#myContent  {
    position: relative;
    width: 100%;
    column-count: 3;
    column-gap: 20px;
    column-break-inside: avoid;
    -moz-column-break-inside:avoid;
    -webkit-column-break-inside:avoid;
}

.imagecontainer {
    position: relative;
    margin-bottom: 20px;
    img {
        position: absolute;
        top: 0;
        left: 0;
        height: auto;
        width: 100%;
    }
}

Example jsFiddle:

https://jsfiddle.net/g0yjd9ov/1/

The elements should align at the top of each column but, instead, the margin-bttom on the element (imagecontainer) at the bottom of the first or second column is being carried over to the second or third column and orphaned, giving the impression of the next element having a margin-top value and breaking the top alignment. This serves no benefit to any situation that a deliberate margin-top value couldn't recreate. I've randomised the heights of the elements just for this example, so occasionally the problem won't show (emphasising how much of a nuisance it is. It's hard to deliberately show). Just refresh if it doesn't, as it occurs quite frequently.

Community
  • 1
  • 1
biscuitstack
  • 11,591
  • 1
  • 26
  • 41
  • Can you give us a working demo of what you have now? – Michael Coker Mar 21 '17 at 20:48
  • 1
    I will. I'm out of the office for a little while so excuse the short delay until I do. Thanks. – biscuitstack Mar 21 '17 at 23:09
  • What is the purpose of the `.imagetextcontainer`? As it stands, it seems to interfere with the size of the container for the image. Also, is the lazy loading plugin you use [this one](https://github.com/ivopetkov/responsively-lazy/)? Just to be sure. – Just a student Mar 23 '17 at 13:35
  • An explanation of `.imagetextcontainer` would potentially affect the clarity of the question, so I decided against it, but their inclusion in the code was so that any solution was aware of their presence. I've tested that their omission does not affect the problem. They are absolutely positioned using 100% width and height. I've edited the question to link to the plugin I'm using. – biscuitstack Mar 23 '17 at 13:42
  • 1
    I just realized that I cannot even reproduce the problem with the margins in the next column (latest versions of Firefox and Chrome). Do you see the problem in [this pen](https://codepen.io/meredevelopment/pen/memwJb) (taken from [this question](//stackoverflow.com/q/32969069/962603))? (I had to play a bit with the width of the container to get the text to line up exactly right, but still did never get margin carrying over.) – Just a student Mar 25 '17 at 09:32
  • @Justastudent Yes, I see the problem in that pen. Conversely, do you see the problem in my example? From my reading, it was a bug across all current browsers but perhaps some recent releases have addressed it. I've just noticed it's no longer in Chrome, yes. It's still present in the current release of Safari and the Technology Preview. – biscuitstack Mar 27 '17 at 09:14
  • I cannot reproduce the problem in Firefox and Chrome, did not test Safari. On the other hand, I saw some strange offset in the first column in Firefox. It still works a bit quirky when margins are involved, so it is definitely better without them. – Just a student Mar 27 '17 at 10:57

3 Answers3

1

After a lot of back and forth I came up with an answer that fixes the problem and maintains the original's CSS properties. There seems to have been two problems causing this.
1 - margins were translating into the next column (but the element was staying into its own column)
2 - height was confined (was a problem in the solution)

solution: since the entire element stays in its own column but the margin is technically not part of the element (it can be tested using outline) this problem can be solved by placing .imagecontainer into another div which has padding instead of margin. (so then its considered one object so the margin problem is avoided) you also would need to move column-break-inside to the parent element so it registers without the margin. a live version can be found at: https://jsfiddle.net/36pqdkd3/6/

1

This can be solved using an extra container that has padding on it. The container for the image (.imagecontainer) should contain only the image, as that is what its aspect ratio is set up for. The space between a block and the next block in the column can be achieved by setting a padding on that block. The block then gets the styling that prevents column breaks from occurring.

I have created a demo that builds upon the example code in the question, but also includes some things that are only described in the question text. This because I wanted to make sure that everything works the way I think it should (let me know if I misinterpreted the question).

In particular, I added the lazy sizes plugin and let it load some placeholder images. I also added some styling and added blocks that contain more than just a single image. For the rules that prevent breaks inside an element, I used some slightly different ones, as per this answer. Finally, I positioned the .imagetextcontainer as discussed in the question comments.

The demo can be found on JSFiddle. I also include it as a code snippet here.

#columncontainer  {
  width: 100%;
  column-count: 3;
  column-gap: 10px;
}

.block-wrap {
  width: 100%;
  padding-bottom: 10px;
  
  /* prevent column breaks in item
  * https://stackoverflow.com/a/7785711/962603 */
  -webkit-column-break-inside: avoid; /* Chrome, Safari */
  page-break-inside: avoid;           /* Theoretically FF 20+ */
  break-inside: avoid-column;         /* IE 11 */
  display:table;                      /* Actually FF 20+ */
}

.block {
  width: 100%;
  background-color: #ffff7f;
}

.block > p {
  margin: 0;
  padding: 10px;
}

.imagecontainer {
  position: relative;
  width: 100%;
  height: 0;
  background-color: #a00;
}

.imagecontainer > img {
  width: 100%;
}

.imagetextcontainer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
<script src="https://afarkas.github.io/lazysizes/lazysizes.min.js"></script>

<div id='columncontainer'>

  <div class="block-wrap">
    <div class="block">
      <div class='imagecontainer' style='padding-bottom: 50%;'>
        <img class='lazyload'
             data-sizes='auto'
             data-srcset='https://placehold.it/100x50/a00/fff  100w,
                          https://placehold.it/200x100/050/fff 200w,
                          https://placehold.it/400x200/057/fff 400w' />
        <div class='imagetextcontainer'>
          <div class='vertaligncontainer'>
            <p class='imagename'>Name</p>
            <p class='imagedesc'>Description</p>
          </div>
        </div>
      </div>
      <p>Some text. Followed by another figure.</p>
      <div class='imagecontainer' style='padding-bottom: 100%;'>
        <img class='lazyload'
             data-sizes='auto'
             data-srcset='https://placehold.it/100x100/a00/fff 100w,
                          https://placehold.it/200x200/050/fff 200w,
                          https://placehold.it/400x400/057/fff 400w' />
        <div class='imagetextcontainer'>
          <div class='vertaligncontainer'>
            <p class='imagename'>Name</p>
            <p class='imagedesc'>Description</p>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="block-wrap">
    <div class="block">
      <div class='imagecontainer' style='padding-bottom: 50%;'>
        <img class='lazyload'
             data-sizes='auto'
             data-srcset='https://placehold.it/100x50/a00/fff  100w,
                          https://placehold.it/200x100/050/fff 200w,
                          https://placehold.it/400x200/057/fff 400w' />
        <div class='imagetextcontainer'>
          <div class='vertaligncontainer'>
            <p class='imagename'>Name</p>
            <p class='imagedesc'>Description</p>
          </div>
        </div>
      </div>
      <p>Some text. No figure here.</p>
    </div>
  </div>

  <div class="block-wrap">
    <div class="block">
      <p>Only text here.</p>
    </div>
  </div>

  <div class="block-wrap">
    <div class="block">
      <div class='imagecontainer' style='padding-bottom: 50%;'>
        <img class='lazyload'
             data-sizes='auto'
             data-srcset='https://placehold.it/100x50/a00/fff  100w,
                          https://placehold.it/200x100/050/fff 200w,
                          https://placehold.it/400x200/057/fff 400w' />
        <div class='imagetextcontainer'>
          <div class='vertaligncontainer'>
            <p class='imagename'>Name</p>
            <p class='imagedesc'>Description</p>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="block-wrap">
    <div class="block">
      <div class='imagecontainer' style='padding-bottom: 100%;'>
        <img class='lazyload'
             data-sizes='auto'
             data-srcset='https://placehold.it/100x100/a00/fff 100w,
                          https://placehold.it/200x200/050/fff 200w,
                          https://placehold.it/400x400/057/fff 400w' />
        <div class='imagetextcontainer'>
          <div class='vertaligncontainer'>
            <p class='imagename'>Name</p>
            <p class='imagedesc'>Description</p>
          </div>
        </div>
      </div>
    </div>
  </div>

  <div class="block-wrap">
    <div class="block">
      <p>Only text here.</p>
      <p>Tow lines now.</p>
    </div>
  </div>

  <div class="block-wrap">
    <div class="block">
      <div class='imagecontainer' style='padding-bottom: 200%;'>
        <img class='lazyload'
             data-sizes='auto'
             data-srcset='https://placehold.it/100x200/a00/fff 100w,
                          https://placehold.it/200x400/050/fff 200w,
                          https://placehold.it/400x800/057/fff 400w' />
        <div class='imagetextcontainer'>
          <div class='vertaligncontainer'>
            <p class='imagename'>Name</p>
            <p class='imagedesc'>Description</p>
          </div>
        </div>
      </div>
    </div>
  </div>

</div>
Community
  • 1
  • 1
Just a student
  • 10,560
  • 2
  • 41
  • 69
  • 1
    Works a charm and I feel a fool for not thinking of something, such as this, that was right under my nose. I really dislike adding container elements to function as nothing other than a hack but it looks like this has been thoroughly investigated at this time and is the simplest solution available. Just to note: `.imagecontainer` doesn't need to contain just the image. My nested elements that are absolutely positioned are not affecting this successful solution. – biscuitstack Mar 27 '17 at 09:10
0

Here is a solution using flex-boxes

HTML

<div id='columncontainer'>

    <div class='imagecontainer'>
        <img class='lazyload'>
        <div class='imagetextcontainer'>
            <div class='vertaligncontainer'>
                <p class='imagename'>Text</p>
                <p class='imagedesc'>Text</p>
            </div>
        </div>
    </div>

    <div class='imagecontainer'>
        <img class='lazyload'>
        <div class='imagetextcontainer'>
            <div class='vertaligncontainer'>
                <p class='imagename'>Text</p>
                <p class='imagedesc'>Text</p>
            </div>
        </div>
    </div>

    <div class='imagecontainer'>
        <img class='lazyload'>
        <div class='imagetextcontainer'>
            <div class='vertaligncontainer'>
                <p class='imagename'>Text</p>
                <p class='imagedesc'>Text</p>
            </div>
        </div>
    </div>

    <div class='imagecontainer' style=''>
        <img class='lazyload'>
        <div class='imagetextcontainer'>
            <div class='vertaligncontainer'>
                <p class='imagename'>Text</p>
                <p class='imagedesc'>Text</p>
            </div>
        </div>
    </div>


</div>

CSS

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

#columncontainer  {
    position: relative;
    width: 100%;
    display: flex;
    height: 40%;
    /* use this if you want containter wrapping
    flex-wrap: wrap; */
    /* use this if you want container scrolling
    overflow: auto; */
    /* use this if you want container scrolling
    justify-content: center; */ 
}

.imagecontainer {
    min-width: 250px;
    position: relative;
    background-color: red;
    overflow: hidden;
    height: 100%;
    margin: 5px;
    z-index: 1;
}

example in jsfiddle

Here are some recommendations based on your question.

I would also recommend learning more about flex-boxes, that is very equipped to handle these kinds of situations.

by default the html and body elements default size is width 100% and no height.

here is a reference I use on flex-boxes

addressing aspect ratio
you can handle aspect ratio using px and vw/vh (measurements of DOM width and height). try playing around with min/max/width to find the perfect amount. (1:2 aspect ratio)

element {
  min-width: 10vw;
  min-height: 20vh;
  width: 50px;
  height: 100px;
}

css size units in my version you can edit ".imagecontainer".

  • Thank Akiva. Your solution appears to remove functionality of responsive layout that columns provide, such as the `imagecontainers` flowing under one another until the bottom of the column, then starting in the next (see my jsFiddle). I've edited my question to add further clarity. This is the important to the responsive layout. In regards to inline styles: This is the recommended procedure for a particular hack for avoiding reflow with lazy loading images. See the link in my question. It is very much a hack, as the name implies, and not an elegant solution. – biscuitstack Mar 23 '17 at 15:55
  • from what i gather from your comment, you want it to flow in the column instead of row. this can be achieved by adding to the `#columncontainer` `flex-flow: column (or column-reverse)` and changing the height `height: 100%` then in the`.imagecontainer` add `min-height: e.g 300px` if you read through my original fork's css, I have commented out some parts if you want different outcomes. this is demonstrated here: https://jsfiddle.net/nfq7p63t/ – Akiva Silver Mar 23 '17 at 16:07
  • Incorrect. Most properties of my example are dynamic, as per my description, so don't see the layout I gave as static in any respect. Most notably, I don't want it to flow as a row or a column. I want it to flow as per the properties of a standard column-count block div. Please see the jsFiddle again, but I can describe this as elements being arranged top to bottom per column, then columns running left to right, in that order, with the possibility of the column count changing and the number of elements, as well as their height and width changing to any number on window resizing. – biscuitstack Mar 23 '17 at 17:19
  • is this how its supposed to look? https://jsfiddle.net/Lwd188tn/1/ (i'm assuming your trying to get rid of that slight bump down in the second column) – Akiva Silver Mar 23 '17 at 17:44
  • With respect, I must emphasise again how dynamic most properties on the page will be. Your example uses static column layouts that cannot account for varying element properties on a responsive page layout. – biscuitstack Mar 23 '17 at 17:48
  • i think i get where you're driving at, but i just want to clarify some of the answer requirements. Do you want the column count to by dynamic, and do you want all the column lengths to end as close to each other as possible? – Akiva Silver Mar 23 '17 at 17:57
  • 1
    StackOverflow discourages comment conversations going on this long so I'll just add that many of the things i've said here and you've raised are addressed in the original question and through familiarity with column-count and what situations it benefits in terms of responsive layouts and dynamic properties. I do appreciate the time you took to tackle this though. – biscuitstack Mar 23 '17 at 18:03
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138868/discussion-between-akiva-silver-and-biscuitstack). – Akiva Silver Mar 23 '17 at 20:08