David Mark's Javascript Tip Du Jour - Volume #1 - Tip #1234 - How toMeasure Element Dimensions

Discussion in 'Javascript' started by David Mark, Nov 10, 2011.

  1. David Mark

    David Mark Guest

    Yes, depending on needs, I'd say that's a considerable drawback or of
    no benefit. Trying to do too much "normalization" is one of the
    script's problems. As mentioned, it screws up height/width in similar
    fashion, making them unusable in some contexts (e.g. border box
    model).

    Considering what a function will be used for is of primary concern for
    a solid design. Unfortunately, when you've got thousands of users and
    contributors, each with their own specific needs, it's hard to reach a
    consensus on use cases. You end up with mush that sloshes back and
    forth over the years as the code base is pulled and pushed by
    competing concerns. Also see attr/removeAttr and the recently
    introduced prop/removeProp.
     
    David Mark, Dec 3, 2011
    #41
    1. Advertisements

  2. David Mark

    David Mark Guest

    I see you have almost *one million* posts on here. Punter. :)

    And who is fighting what? Just throw them away.
    I've seen it. Somebody doesn't like Modrnizer (sp?). I don't like it
    either. Imagine, a monolithic feature detection script that
    pigeonholes browsers according to some predetermined series of
    "standard" tests. Adds a ton of classes too. No thanks!
     
    David Mark, Dec 3, 2011
    #42
    1. Advertisements

  3. David Mark

    David Mark Guest

    Should amend and sorry for quoting sig. Was on phone browser. Glad
    to get out without losing the message in a crash. :(

    Can think of one recent case where I compared scrollWidth to
    clientWidth; it was the usual case of expanding an overflow:auto
    container to exactly fit the width of the content. Had no problems
    whatsoever; tested in almost every version of IE, as well as several
    versions of the usual standards-based suspects. From time frame, it
    seems to me that Opera 10 would have been on that list. But I know
    there weren't issues with IE in my context. Version 8 was primary IE
    browser at the time and tested back to 5.5 in IETester or some such.

    It wouldn't shock me if in some version in some rendering mode (e.g.
    quirks) or some compatibility mode of IE, and with some particular
    styling and structure, there is a bug with the scrollWidth property.
    But AFAIK, it never bit me. :)

    ISTM that the "drag and scroll" example on the Build Test page would
    be a good place to start to determine if IE 8 or Opera 10's
    implementation of scrollWidth/Height is off in some way. If they are
    off, there's no way that example will work properly (unless it just
    happens to avoid the bugs by coincidence of particular styling or
    other variables).

    And if there are such quirks with those properties, they should be
    trivial to feature test. ;)
     
    David Mark, Dec 3, 2011
    #43
  4. David Mark

    David Mark Guest

    Tricky. :)

    Your best bet is to see the tip of the day related to coordinates.
    From that you should be able to get the coordinates of the element
    relative to the viewport (or "window" as I think I referred to it in
    that post). So you will need the mouse coordinates relative to the
    viewport. Ironically enough, it's trivial to find those in the old IE
    versions that lacked pageX/Y properties. But for everything else, you
    have to have to subtract the scroll position as pageX/Y are relative
    to the document. Depending on context, getting the scroll position is
    either trivial (one-liner) or fairly complex. Frustratingly, it's IE
    (all versions AFAIK) that needs most of the extra code in this case.
    You also have to consider other factors like whether you will need
    this to work in quirks mode (hopefully not)


    It looks like:-

    // No quirks mode, frames or other windows (just the one running the
    script)

    var getScrollPosition;

    if ('number' == typeof window.pageXOffset) {

    // Many "standards-based" browsers feature this non-standard
    property
    // No ambiguity about what this window property means

    getScrollPosition = function() {
    return [window.pageXOffset, window.pageYOffset];
    };
    } else if (document.documentElement && 'number' == typeof
    document.documentElement.scrollTop) {

    // Proprietary IE properties, copied widely by others; often 0,0 in
    mobile browsers,
    // Ambiguous as many mobiles represent an un-scrolled document (i.e.
    scrollHeight == clientHeight), regardless of which portion of document
    is viewable

    getScrollPosition = function() {
    return [document.documentElement.scrollLeft,
    document.documentElement.scrollTop];
    };
    }

    As usual, simplified feature detection (use isHost* functions) and
    untested. Paste at your own risk.

    One other thing, I wouldn't trust that you can get the mouse position
    in a click or dblclick listener. Not sure what the standard
    recommendation is, but I know I never retrieve the position from
    anything but mousedown/move/up. ISTM that, at least traditionally,
    that's all you can expect to work reliably. And by "work", I mean
    across most browsers released this century. There's no way anything
    like this can be considered cross-browser. There's at least a couple
    of other pairs of properties where at least some mobiles insist on
    stashing their "scroll" position (offsetLeft/Top comes to mind). So
    there's at least one more prong and not based on any sort of logic,
    just observation.

    Needless to say, getting the "viewport" scroll position is something
    to avoid whenever possible (most of the time). But as soon as you
    start with the mouse position, at least for years to come, you
    introduce that dependency.

    Having said all of that, perhaps somebody out there who has actually
    needed to tackle this problem has a cleaner, context-specific solution
    that works in an acceptable subset of browsers. The big issue is that
    there is no way to feature test the scroll position in advance, short
    of actually setting the scroll position (which is clearly out of the
    question).
     
    David Mark, Dec 3, 2011
    #44
  5. David Mark

    J.R. Guest

    window.pageYOffset is available as of IE9, FF3+, Safari 4+, Chrome 4+,
    Opera 10+.

    IE 8 and older browsers in standards-compliant mode (strict mode) would use:

    if (document.documentElement && document.documentElement.scrollTop) {
    return document.documentElement.scrollTop;
    }

    But IE in quirks mode (tested with IE8) would use:
    if (document.body) {
    return document.body.scrollTop;
    }

    --
    Joao Rodrigues (J.R.)

     
    J.R., Dec 3, 2011
    #45
  6. David Mark

    David Mark Guest

    I suspected MS might add those at some point, but never had cause to
    confirm. Thanks.
    Something like that.
     
    David Mark, Dec 3, 2011
    #46
  7. David Mark

    David Mark Guest

    So let's finish this off. If you need it to work in IE 9 and
    virtually every other browser made since the turn of the century:-

    if ('number' == typeof window.pageXOffset) {
    getScrollPosition = function() {
    return [window.pageXOffset, window.pageYOffset];
    };

    // I suppose jQuery users would prefer gPtrPosRel2Win or some such
    "concise" BS. :)

    // Works for mouse or touch

    getPointerPositionRelativeToViewport = function(e) {
    return [e.pageX - window.pageXOffset, e.pageY -
    window.pageYOffset]
    };
    }

    Piece of cake (as this context usually is).

    Now, what if you need IE 6-8 to join in the fun?

    Put this script inside conditional comments (so the others don't
    download it):-

    // Last test is to exclude IE quirks mode and IE 5

    if (document.documentElement && 'number' == typeof
    document.documentElement.scrollLeft &&
    document.documentElement.clientWidth) {
    getScrollPosition = function() {

    // Note: optimize by saving a reference to documentElement (saves
    two dot operations)

    return [document.documentElement.scrollLeft,
    document.documentElement.scrollTop];
    };

    getPointerPositionRelativeToViewport = function(e) {

    // Yes, we would use these in non-IE browsers too if history of
    implementation
    // wasn't atrocious. Perhaps in a few years...
    // Regardless, you know for sure these work as advertised in
    legacy IE versions

    // NOTE: the HTML border "issue" is irrelevant as same offset for
    element positions

    return [e.clientX, e.clientY]
    };
    }

    // Detect API features (never changes, regardless of specific
    renditions used)
    // Self-documenting (really, not like jQuery's claims)

    if (getScrollPosition && getPointerPositionRelativeToViewport) {

    var el = getElementById('johnstockton');

    el.onmousedown = ...
    el.ondblclick = ...


    }

    Will leave rest as an exercise. Hint: need to add
    getElementRectangleRelativeToViewport (see
    getElementPositionRelativeToViewport from previous tip). Also need
    isPointInRectangle, which is trivial and browser agnostic (so no need
    to make that function's creation conditional).

    That solution should work in virtually every browser ever made.
    Untested; no warranty implied.

    Not to beat a dead horse, but would love to see this attempted with
    jQuery, jQuery UI, jQuery Mobile or whatever. You are pretty much
    screwed before you start with those things as the context is lost in a
    mudslide of confused logic and bad inferences. Or perhaps I'm just
    jealous or trying to steal their "business". :)

    Is learning so distasteful as to justify dumping any old piece of junk
    on your hapless users? Nothing loses customers faster than a half-
    functioning UI. ;)
     
    David Mark, Dec 4, 2011
    #47
  8. David Mark

    David Mark Guest

    And actually, the way it ended up (with the two functions working
    independently), don't even need getScrollPosition. Could have written
    getPointerPositionRelativeToViewport to call the getScrollPosition
    function, but that would just slow things down (and retrieving pointer
    position needs to be as fast as possible). Better to render the two
    functions as a unit (if the other one is needed at all).

    if (getPointerPositionRelativeToViewport) {
    var el = getElementById('johnstockton');
    el.onmousedown = ...
    el.ondblclick = ...
    }
    And may not need the latter either; depends on what this is to be used
    for (was envisioning a single canvas with regions delineated by
    drawing). As always, YMMV.

    Don't forget to buy the book when it comes out. It's got hundreds of
    renditions like these, covering virtually every common browser
    scripting task. Alternatively, you can download some context-less
    blob of BS from the Web and pray it works for at least some of your
    visitors for a while; then download another, and another... And
    eventually those "best minds in the industry" (quote from a recent
    blog post) will solve all of *your* problems perfectly (as if they
    know you).
     
    David Mark, Dec 4, 2011
    #48
  9. David Mark

    David Mark Guest

    BTW, it's certainly in every FF and Chrome. Likely Safari too. And
    Opera as far back as I can remember.

    Are these PPK numbers? :)

     
    David Mark, Dec 4, 2011
    #49
  10. Which book? Have I missed something?

    Hans-Georg
     
    Hans-Georg Michna, Dec 4, 2011
    #50
  11. David Mark

    David Mark Guest

    Not yet. :)
     
    David Mark, Dec 4, 2011
    #51
  12. David Mark

    David Mark Guest

    A solution. This is the "good riddance to bad baggage" environment
    context.

    // This function fades away in IE 8- and compatibility modes

    if ('number' == typeof window.pageXOffset) {

      // Works for mouse or touch

      getPointerPositionRelativeToViewport = function(e) {
        return [e.pageX - window.pageXOffset, e.pageY -
    window.pageYOffset]
      };
    }

    /*
    * Need these two API functions
    * See position tip for first function
    * Should need the normalized rendition that subtracts
    documentElement.clientLeft/Top
    * as comparing element position to pointer position results that
    don't have the
    * client-related quirk (i.e. getBoundingClientRect, clientX/Y not
    involved)
    * Quirk must be present or absent on both sides of the equation to be
    factored out. ;)
    */

    if (getElementPositionRelativeToViewport &&
    getPointerPositionRelativeToViewport) {
      var el = getElementById('johnstockton');
    var pointerPosition;

    // NOTE: should only respond to one event type at a time

      el.onmousedown = el.ontouchstart = function(e) {
    pointerPosition = getPointerPositionRelativeToViewport(e);
    };

      el.onclick = function(e) {

    /*
    * Make sure a mousedown/touchstart preceded the click on this
    element
    * If another mousedown listener hid an element that was covering
    * the "johnstockton" element, could get click without mousedown
    */

    if (pointerPosition) {
    var elementPosition =
    getElementPositionRelativeToViewport(this);
    window.alert([pointerPosition[0] - elementPosition[0],
    pointerPosition[1] - elementPosition[1]]);

    // One click per mousedown/touchstart

    pointerPosition = null;
    }
    };
    }

    If wrapped in a function, be sure to set el to null (to break circular
    references). As usual, no warranty, may not be appropriate for your
    needs, etc.

    As mentioned, you can add this to create a legacy IE rendition,
    perhaps even putting it inside conditional comments, loading it only
    for version 8 and under (which will also catch compatibility mode).

    if (document.documentElement && 'number' == typeof
    document.documentElement.scrollLeft && 'number' == typeof
    document.documentElement.clientLeft &&
    document.documentElement.clientWidth) {
      getPointerPositionRelativeToViewport = function(e) {
        return [e.clientX - document.documentElement.clientLeft,
    e.clientY - document.documentElement.clientTop]
      };
    }

    Note that this is also a normalized rendition as the results will be
    compared to the results of the normalized element position function
    that accounts for IE's chrome HTML border. Quirk is absent on both
    sides of the equation.

    It should be mentioned that it is best not to put borders on the HTML
    element, otherwise ambiguity is introduced in the relationship between
    clientLeft/Top values (normally 0) and element coordinates. Certainly
    HTML borders are not part of the Chrome in other browsers that copied
    getBoundingClientRect, so it is unrealistic to assume they treated the
    HTML borders the same. Last I heard somebody was trying to stamp a
    standard on this mess (and good luck to them), but that will only make
    a difference on paper.

    IIRC, all works out even with HTML borders in other browsers, but why
    tempt fate? Probably doesn't need mentioning that putting borders on
    the HTML element is crazy anyway. This is the sort of non-issue that
    public libraries love to tackle.

    I spent hours putting margins and borders on the HTML (body in quirks
    mode) element when testing My Library. I suppose I could justify the
    waste of time as an academic exercise, but wouldn't recommend using
    the results over functions like the above. As soon as you lose sight
    of your context, extraneous complications start to appear. This
    happens automatically in projects that involve collaboration with lots
    of other developers, each likely thinking in the context of their
    current (downstream) efforts.

    Doesn't take long before the best hint you can give about the
    functions is that they pass some tests in some recent versions of
    Chrome, Safari, Opera, Firefox and some versions/modes of IE. Other
    browsers, which is the what today's browsers will be tomorrow, are
    anyone's guess. The oft-heard line: "we don't care about [browser x]"
    is truly indicative of a careless, cavalier and ultimately futile
    approach to browser scripting.

    Unless you want to be on an endless treadmill, "keeping up" with five
    (then seven, then nine...) browsers, you want cross-browser code,
    which tests features and does *nothing* in environments that fail.
    Therefore, during development and testing, it is necessary to load a
    script in at least *one* browser that is not expected to pass the
    tests (usually an old browser). The jQuery way of wearing blinders
    and quoting recent browser version numbers as the starting point of
    their caring is a perfectly ridiculous strategy, particularly as rapid
    change in browser differences (which jQuery ostensibly buffers) is
    what neophytes fear the most. If the library developers are that
    focused on Chrome 4+, Opera 10+, etc., you have to wonder how they
    managed to screw up Opera 9- or Chrome 3-; probably because they were
    so focused on Opera 9+ and Chrome 3+ at the time. This sort of
    nonsense has been going on since the late 90's. Just steer clear of
    projects with five browser icons lined up to indicate their perceived
    "compatibility". Think Dynamic Drive with three more icons. ;)

    Nothing is guaranteed, but cross-browser code has the best shot at
    lasting from one cycle of browser "innovation" to the next and to fade
    away gracefully as today's browsers go the way of Netscape 6.

    When you consider mobile users, most are using "other browsers"
    today. The browser in Android devices is not Chrome. iOS devices do
    not run the same Safari as found on desktops. Then there are the
    mobile versions of Opera and Firefox and the new Windows phones, which
    are clearly not exactly the same as their desktop counterparts. And,
    other than fortune tellers, who knows what's next?
     
    David Mark, Dec 4, 2011
    #52
  13. David Mark

    David Mark Guest

    Didn't make this very clear. I mean you will get dupes with touch
    input (both touchstart and mousedown)
    Oops. Didn't use "addEvent" wrapper, so no "e" in IE.

    if (!e) {
    e = window.event;
    }

    Normally would use a wrapper and hopefully one that avoids the
    circular reference issue as well. Follow the JSPerf link on original
    post to find a couple of those. It's ugly to have such IE-isms in the
    application code.
    Don't actually need "e" here.
     
    David Mark, Dec 5, 2011
    #53
  14. David Mark

    David Mark Guest

    Kind of trailed off there, didn't I? :) See attachTouchListeners in
    Touch add-on. If it gets touch events, it ignores corresponding mouse
    events. Doesn't matter so much in this example where the mousedown
    listener will just overwrite the previously stored array reference
    with a reference to an identical array. ISTM that if "touch" objects
    (or just one) present, one is to be used in lieu of the event object.
    So here, if not using a function like attachTouchListeners, would need
    to set e to reference a touch object. The touch object is passed to
    getPointerPosition*, not the event object. When using that wrapper,
    the event object is irrelevant as target and touch object passed to
    listeners and returning false prevents the default behavior.

    Should have probably left out the touch stuff from this example.
    Sorry for any confusion. :)
     
    David Mark, Dec 5, 2011
    #54
  15. David Mark

    David Mark Guest

    Should also point out that the window.event substitution is unneeded
    unless using legacy IE rendition. If not, "addEvent" wrapper adds
    nothing to example. And as performance a non-issue,
    attachTouchListeners (or ontouchstart) use unnecessary.
     
    David Mark, Dec 5, 2011
    #55
  16. <snip>

    Please please shorten your quoted text. My scroll wheel is getting red
    hot.

    John
     
    John G Harris, Dec 5, 2011
    #56
  17. David Mark

    David Mark Guest

    Yeah, I need to stop responding in mobile browser.
     
    David Mark, Dec 5, 2011
    #57
  18. David Mark

    Frobernik Guest

    This should get Platinum Czar Punter post of 2011 I think!

    If only it were that simple - most designers and a lot of developers
    would rather copy and paste or be hypnotised by Googled bulls**t than
    actually get familiar with the browser or even do some work :(
    Moronizers own website is ridiculous. The custom font typefacing has had
    to be hidden until the page finishes loading (thats assuming the JS
    finishes loading!) *face palm*

    Frobernik
     
    Frobernik, Dec 6, 2011
    #58
  19. David Mark

    David Mark Guest

    That's very colorful, but don't understand a word of it. :)
    Correct. But designers can get away with it as they only need their
    proof of concept to work in one browser (the one they use to demo).
    Trouble is that inexperienced and overconfident developers think they
    can just test the mock-up in a few more browsers and throw it on the
    client's server. This poor behavior is positively reinforced by
    clients who just don't know any better.
    Yes, that sounds pretty backwards to me. I see lots of sites that
    combine that thing with jQuery and jQuery plug-ins. An "HTML5
    modernizer" combined with a bunch of old IE6/7 hacks? It's the Sybil
    pattern. :)

    As you mentioned, many developers just window shop for these silly
    scripts like they were designers. They pile on all everything that
    looks cool in their installed browsers and wait for the inevitable
    call back when it all falls apart. Their ready excuse is that "nobody
    is perfect"; but there is a large gulf between absolute perfection and
    gross incompetence. ;)

    Just had one of those clods dial in on Twitter. They said that
    something was wrong with some of the old My Library examples on the
    new Amazon "Silk" browser (Kindle Fire). Their juxtaposition was that
    "jQuery UI works". :)

    Of course, jQuery UI is completely inappropriate for mobile devices
    (or anything else, really). I wouldn't find it surprising that
    whatever jQuery demo they were peering at seemed to "work" in a
    browser that the jQuery developers were peering at when they wrote the
    thing. The question is whether it should be expected to work in
    browsers unknown to the developers (e.g. future versions, new
    browsers, new devices, etc.). History and good sense say no as such
    projects are always about overreaching to "keep up" with current,
    popular browsers, usually at the expense of others (the browsers the
    developers claim not to "care" about).

    And if jQuery UI works so well on mobile, why is there a jQuery
    Mobile? Even more curious, how can anyone even say "jQuery Mobile"
    with a straight face? I just saw a blog that mentioned they
    "finalized" the thing recently, and it went on to list five or six
    browsers (with specific version numbers, of course) that it
    "supports". What do you guess it does in every other browser released
    this century? Finalized?! For how many weeks? Seems these guys
    never learn.

    Developers should wake up and realize that relying on bored hobbyists
    to ferret out and "fix" bugs, one browser version at a time, is
    futile. They should be learning to *avoid* bugs, not trying to find
    them. They sure as hell shouldn't be sending out teams of neophytes
    to find (and often misinterpret) issues.

    Many developers think browsers are far buggier than they are, simply
    because they have spent years watching projects like jQuery stumble,
    bumble, fumble, etc. This reinforces their perceived need for
    jQuery. After all, if the "best minds in the industry" are having
    such a time of it... It's a self-perpetuating cycle. jQuery makes it
    look really difficult and designers/developers assume such frustration
    is par for the course.
     
    David Mark, Dec 6, 2011
    #59
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.