19

What is the most elegant way to check whether an element is in the normal flow using jQuery?

According to the CSS3 specification,

A box belongs to the flow if:

The used value of its ‘display’ is ‘block’, ‘list-item’, ‘table’ or template.

The used value of its ‘float’ is ‘none’.

The used value of its ‘position’ is ‘static’ or ‘relative’.

It is either a child of the flow root or a child of a box that belong to the flow.

Should I just check for all these conditions, or is there a better way?

Stas Bichenko
  • 13,013
  • 8
  • 45
  • 83
  • 3
    My first question is, why do you need to know this? You probably only need to test one of these qualities in your actual application. – Blazemonger Dec 11 '12 at 19:23
  • If this is a definition used only in the specification, you're best off checking the actual conditions. – pimvdb Dec 11 '12 at 19:25
  • I'm working on a personal side-project which involves copying and pasting styled content in browser. I want to remove top margin of the first element in flow and the bottom margin of the last element in flow. – Stas Bichenko Dec 11 '12 at 19:25
  • If you need to quote CSS3 specs on what it means the element is in the flow, it means that it differs a bit from your actual need. By the way, IMHO, if you won't even know upfront what the pasted elements will be, you're taking up a task that involves more than just jQuery, and you'll need to know some vanilla js as well. – tonino.j Dec 11 '12 at 19:36
  • And combining jQuery + js testing for each of these cases, shouldn't be so hard. It's just ten lines of code. – tonino.j Dec 11 '12 at 19:38
  • That specification is old (5 years old!) and slated for a rewrite. Specifically, in the section that you quote, * – BoltClock Dec 15 '12 at 08:05
  • Could you define 'flow'? Are you talking like W3C standards? – bobthyasian Dec 15 '12 at 08:51

3 Answers3

4

I doubt theres a better way, but a different way would be:

1) Surround the element with a wrapper

2) Compare height and width of wrapper with wrapped element

For example:

$('#elementToTest').clone().addClass('clone').wrap('<div></div>')
if($('#elementToTest.clone').height()>$('#elementToTest.clone').parent().height()){
    //outside the normal flow
}
A F
  • 7,424
  • 8
  • 40
  • 52
  • Sorry, I mean: how will this check whether an element is in normal flow? – Stas Bichenko Dec 12 '12 at 17:55
  • 3
    Elements out of flow do not give their parents height or width. – A F Dec 12 '12 at 18:30
  • 5
    Interesting technique, however it does seem adding a wrapper `div` could cause a misread based on how selectors are applying on `#elementToTest`. For instance, if what is causing `#elementToTest` to be out of flow is a selector like `.wrapper > .originalElem {position: absolute}` then wrapping a clone of it with a `div` breaks the direct child relation that the selector was looking for. A sibling selector and perhaps others could be broken by this method. So while your solution could be useful if one knows exactly what they are expecting, it would not make a good general solution. – ScottS Dec 15 '12 at 18:38
3

I think another "in flow" requirement is that overflow is set to visible.

From the CSS2 spec:

Floats, absolutely positioned elements, block containers (such as inline-blocks, table-cells, and table-captions) that are not block boxes, and block boxes with 'overflow' other than 'visible' (except when that value has been propagated to the viewport) establish new block formatting contexts for their contents.

Based on the requirements you quoted and the overflow requirement, this is one way to do it with jquery:

function isInFlow(elm, ctxRoot) {

    ctxRoot = ctxRoot || document.body;

    var $elm = $(elm),
        ch = -1,
        h;

    if (!$elm.length) {
        return false;
    }

    while ($elm[0] !== document.body) {
        h = $elm.height();
        if (h < ch || !okProps($elm)) {
            return false;
        }
        ch = h;
        $elm = $elm.parent();

        if (!$elm.length) {
            // not attached to the DOM
            return false;
        }
        if ($elm[0] === ctxRoot) {
            // encountered the ctxRoot and has been
            // inflow the whole time
            return true;
        }
    }
    // should only get here if elm
    // is not a child of ctxRoot
    return false;
}

function okProps($elm) {

    if ($elm.css('float') !== 'none'){
        return false;    
    }
    if ($elm.css('overflow') !== 'visible'){
        return false;    
    }
    switch ($elm.css('position')) {
        case 'static':
        case 'relative':
            break;
        default:
            return false;
    }
    switch ($elm.css('display')) {
        case 'block':
        case 'list-item':
        case 'table':
            return true;
    }
    return false;
}
​   

See this jsFiddle for test cases.

I'm not sure if it would be better to use window.getComputedStyle() or not.

The function is checking to see if elm is in ctxRoot's flow or block formatting context (as it was previously called, I think). If ctxRoot is not supplied, it will check against the body element. This does not check to make sure ctxRoot is in flow. So, with this HTML

<div id="b" style="overflow: hidden;">
    <div id="ba">ba
        <p id="baa">baa</p>
        <span id="bab">bab</span>
        <span id="bac" style="display:block;">bac</span>
    </div>
</div>

The test cases are:

var b = $('#b')[0];
console.log('no  ',isInFlow(b));
console.log('no  ',isInFlow('#ba'));
console.log('yes ',isInFlow('#ba', b));
console.log('no  ',isInFlow('#baa'));
console.log('yes ',isInFlow('#baa', b));
console.log('no  ',isInFlow('#bab'));
console.log('no  ',isInFlow('#bab', b));
console.log('no  ',isInFlow('#bac'));
console.log('yes ',isInFlow('#bac', b));
Community
  • 1
  • 1
tiffon
  • 5,040
  • 25
  • 34
0

Instead of looking at it retroactively, you could pre-empt the need for this by using data annotations. Any time you create or define an element, set its attribute data-flow to true or false.

For example:

var newDiv = document.createElement("div");
newDiv.style.position = "absolute";
newDiv.setAttribute("data-flow","false");

Or in html

<div style="position:absolute;" data-flow="false"></div>

And then you could simply select these elements with a selector:

$('*[data-flow=false]')
Travis J
  • 81,153
  • 41
  • 202
  • 273