3

I have done a lot of research (including this question) on the topic of how percentage has an impact on margin, but I am yet to calculate the correct algorithm to work out additional height/width added to a margin.

As taken from w3c:

The percentage is calculated with respect to the width of the generated box's containing block. Note that this is true for 'margin-top' and 'margin-bottom' as well. If the containing block's width depends on this element, then the resulting layout is undefined in CSS 2.1.

So if this is true, the following algorithm should work:

width of container = w_c
decided margin = m
x = ((w_c/100)*m) +- unknown

Take the given codepen example.

The two left div's are 42.5% in height and have a margin-bottom of 5%. So theoretically this should equal 90% and match the same height as the right div. But this isn't the case.

I am sure this can be calculated because the following codepen works with margin-bottom: 5vh; instead of 5%. I want this to work with % instead of vh.

So my question is, what would the algorithm be of margin-bottom to calculate the exact margin that would make both div's left, with margin-bottom, add up to 90% and match the div on the right?

Community
  • 1
  • 1
Jonathan Davies
  • 882
  • 3
  • 12
  • 27

2 Answers2

2

I have been speaking with the author, and upon learning (and being convinced) that css margin-top and margin-bottom are indeed not based upon the div's height, I found myself annoyed at the fact and decided to write something. I hope it helps anyone with a similar issue!

Basically, it searches through the document for element tags with the attribute "vertMargin" set to true. If it finds one, it creates a function prototype with 3 variables in it's scope... two for the initial inline margin setting, and one used as a callback, then adds this prototype to an array. Upon resizing the window, it iterates over this array to run the stored callback, resizing the div's margin's appropriately.

Anyway, my solution.

Make a script file with the following code.

var addMarginBottom = function(target){
    var parentDiv = target.parentNode;
    var parentRect = parentDiv.getBoundingClientRect();
    var parentHeight = parentRect.bottom - parentRect.top;
    var height_1Pc = parentHeight/100;
    target.style.marginBottom = height_1Pc*target.bottom_pc+"px";
};

var addMarginTop = function(target){
    var parentDiv = target.parentNode;
    var parentRect = parentDiv.getBoundingClientRect();
    var parentHeight = parentRect.bottom - parentRect.top;
    var height_1Pc = parentHeight/100;
    target.style.marginTop = height_1Pc*target.top_pc+"px";
};

var NODE_REF = [];

var nodeRef = function(){
    var update;
    var top_pc;
    var bottom_pc;
}

var realHeightMarginInit = function(target){
    var children = target.childNodes;
    for(var x = 0; x < children.length; x++){
        var child = children[x];
        if(child.nodeType == 1){ //is an elemental node..
            if(child.hasAttribute("rel-margin")){
                var nodeRef = child;
                var nodeStyle = window.getComputedStyle(nodeRef);
                var parentStyle = window.getComputedStyle(target);
                var num = parseFloat(nodeStyle.marginTop);
                var dom = parseFloat(parentStyle.width);
                nodeRef.top_pc = (num/dom)*100;
                num = parseFloat(nodeStyle.marginBottom);
                nodeRef.bottom_pc = (num/dom)*100;
                nodeRef.update = function(){
                    addMarginTop(nodeRef)
                    addMarginBottom(nodeRef);
                };
                NODE_REF.push(nodeRef);
            }
            realHeightMarginInit(child);
        }   
    }
}

In your HTML file, add these inside some script tags...

window.addEventListener("resize", function(){
    for(var x = 0; x < NODE_REF.length; x++){
        NODE_REF[x].update();
    }
});

window.addEventListener("load", function(){
    realHeightMarginInit(document);
    for(var x = 0; x < NODE_REF.length; x++){
        NODE_REF[x].update();
    }
});

Now you can add an inline style for margin-top and margin-bottom to achieve the behavior you are looking for :) Simply add the attribute vertMargin="true" and it will work as expected.

<div rel-margin id="your-top-left-div">
    Hello!
</div>

This however may very well not be your best option (it is also kind of a mess)! There are tons of tools and techniques you can use to avoid this problem, such as using a CSS framework like bootstrap, and 90% of the time, restructuring your CSS and HTML is perhaps the most straightforward and easiest to implement. Of course, sometimes a js based solution may just be what you need.

EDIT: Updated the code so that styles can now be part of a stylesheet rather than inline.

(Updated) Example of usage with OP's code.

Community
  • 1
  • 1
ZombieTfk
  • 706
  • 7
  • 19
1

While margin does depend on width and can be calculated, there is a percentage height set at a lot of places --- html, body, #Left, #Right....

Percentage margins in this case, won't always add up since included with percentage height. Even if it does, it will fail on window resize.

That means, though

42.5% + 42.5% + 5%

looks like it adds to 90%, it doesn't. Simply because 5% is based on width --- while the other two, are values of height. The wider the containing block the greater the value of 5%.

As far as I understand, its not possible in this case with only CSS. While it may be better to use viewport units, do remember that it works cos of height: 100% set everywhere else (html, body, columns). If you were to change the height property assigned to one of those, you would have to change the margin as well. And calculating that wouldn't be as easy as 5vh.

One way via JavaScript, could be to calculate the separation needed in pixel values using the difference in height between the columns and a window resize event. Do remember to get the actual floating values to best avoid sub-pixel problems. My JS is a little rusty, but hopefully can be reworked as seen fit.

https://jsfiddle.net/j0565smf/

function setMargin() {
  // get them all
  var look = document.getElementsByClassName('Look'),
  // 42.5% + 42.5%
  leftHeight = look[0].getBoundingClientRect().height * 2,
  // height of red div
  rightHeight = look[2].getBoundingClientRect().height,
 
  // separation needed
  separation = rightHeight - leftHeight;

  // set this on the first blue box
  look[0].style.marginBottom = separation + "px";
}
setMargin();
window.addEventListener('resize', function(){
 setMargin()
}, true);
*,html,body {height: 100%;padding: 0; margin: 0;}
#Ticket     {width: 100%;height: 100%;float: left;}
#Left       {width: 40%;height: 100%;float: left;}
#Right      {width: 60%;height: 100%;float: left;}

.Look       {float: left;width: 100%;height: 100%;}
.test1      {background-color: blue;height: 42.5%;}
.test2      {background-color: red;height: 90%;}
<div id="Ticket">
  <div id="Left">
    <div class="test1 Look"></div>
    <div class="test1 Look"></div>
  </div>
  <div id="Right">
    <div class="test2 Look"></div>
  </div>
</div>
AA2992
  • 1,559
  • 3
  • 17
  • 23