Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Measure height of element after content is fully loaded

I am completely new to JavaScript/jQuery/programming in general, so please bear with me if I come across as painfully naive.

I've written a script that converts the footnotes on a Tumblr blog to "sidenotes." It works pretty well, for the most part. You can see examples on my blog.

I do this by appending the innerHTML of the footnotes to an empty div, then hiding the original footnotes with CSS (display: none). I don't want to alter the markup of the posts at all. The idea is that I can remove the script and sidenotes revert or "degrade" back to footnotes.

The sidenotes are absolutely positioned relative to their container, and their position depends on several factors: the position of the preceding sidenote, the position of the reference link in the text, and the position of the container. Ideally, the sidenote should appear at the same vertical offset as the reference link in the text, but if the preceding element is in the way, it shifts down to avoid overlapping.

The script is pretty simple. Basic arithmetic. To avoid overlapping elements, I calculate the offset from the top of the page to the bottom of the preceding element. If its larger than the offset of the reference link (and the offset of the container), the element shifts down.

As you can see, everything works perfectly if the footnotes only contain text. But if they contain images, too, the sidenotes start to overlap. I believe it's because I'm using .outerHeight() to get the height of the element, but if the image hasn't loaded yet, it returns the wrong value. You can see an example of what I'm talking about on one of my test pages.

I think I've isolated the problem to the function below.

I thought that if I used .load, it would wait for the element's content to load before executing the code. But that doesn't seem to be happening.

Please tell me what I might be doing wrong. I'm pretty sure the script's problem is contained in the code below, but if you think this looks fine, I can post the rest of the script as well. Or you can view it at http://resources.contentioninvain.com/scripts/FootnotesToSidenotes.js (Sorry, I'm a new user so I can't post more than two links, haha).

Thanks in advance for any suggestions. This is the first time I've posted a question, but I've found this site really useful in the past. Aside from tinkering, this is the first time I've tried to write a script from scratch (I'm kind of proud that I've gotten this far without help, haha).

// GetBottomOffset gets the vertical offset to the bottom of an element
// It adds the element's vertical offset and its height:
function GetBottomOffset(Element) {
    $(Element).load(function() {
        var ElementTop = Element.offset().top; // Vert offset of element
        var ElementHeight = Element.outerHeight(true); // Height of element

        // Offset to bottom of element
        var ElementBottom = ElementTop + ElementHeight;

        // Returns ElementBottom
        return ElementBottom;
    }); 
}
like image 749
Andrew Clark Avatar asked Jun 30 '11 06:06

Andrew Clark


People also ask

How do you find the height of an element with padding?

Using jQuery, you can get element height including padding with $(ele). outerHeight() , get element height including padding, border and margin by $(ele). outerHeight(true) .

How do you find the content height?

Using jQueryjQuery has the . height() method, which returns an element's content height. For example, the following code returns 100, which is equal to the original height of div.

How do I get the height of a div element?

Method 1: Using the offsetHeight property: The offsetHeight property of an element is a read-only property and used to return the height of an element in pixels. It includes any borders or padding of the element. This property can be used to find the height of the <div> element.

How do you find the height of an element in HTML?

The HTMLElement. offsetHeight read-only property returns the height of an element, including vertical padding and borders, as an integer. Typically, offsetHeight is a measurement in pixels of the element's CSS height, including any borders, padding, and horizontal scrollbars (if rendered).


1 Answers

I think there are three unrelated issues:

  1. I think you'll have to watch individually for the images to load as well, and only do the calculation when everything is loaded.
  2. You can't return the value from the function if you wait for an event to occur first, because the function will have already returned before the event occurs. You'll need to use a callback.
  3. You have to allow for the image already being loaded before you hook the load event.

So for instance:

function watchForBottomDimension(element, callback) {
    var $element, images, loaded;

    // Get a jQuery wrapper for `element`
    $element = $(element);

    // Find the images within it that aren't loaded yet
    images = $element.find("img").filter(function(index, img) {
        return !img.complete;
    });

    // Find any?
    if (images.length === 0) {
        // No, we're done -- call the callback asynchronously for
        // consistency with the case below where we have to wait
        setTimeout(done, 0);
    }
    else {
        // We're waiting for some images, do that
        loaded = 0;
        images.load(function() {
            // One of them loaded; was it the last one?
            if (++loaded === images.length) {
                // Yup, we're done
                done();
            }
        }); 
    }

    // Handle completion
    function done() {
        var top, height, bottom;

        top = $element.offset().top; // Vert offset of element
        height = $element.outerHeight(true); // Height of element

        // Offset to bottom of element
        bottom = top + height;

        // Call the callback with the value
        callback(element, bottom);
    }
}

Some notes on that:

  1. This is off-topic, but it's probably the first thing you'll notice in the above, so: I've changed the names of things. In JavaScript, the overwhelming convention is to use an initial lower case letter for everything except constructor functions (functions you primarily call with new, like Date), which are initially capped, and pseudo-constants which are sometimes done in ALL CAPS. So element rather than Element, doSomething rather than DoSomething, etc.
  2. I'm checking whether the images are loaded via HTMLImageElement#complete. According to that spec, though, there's a race condition because completed could change between when we check it and when we hook the load event (I didn't realize that until I went looking for the reference; surprising), so a more robust approach may be required.
  3. Part of your original code is assuming Element is a jQuery instance, other parts are passing it into $ as though it weren't. In the above I've assumed it's a raw DOM element; if it's a jQuery instance instead, just remove the $element = $(element) bit and use element instead of $element.
  4. Note that my watchForBottomDimension accepts a parameter called callback, which is expected to be a function. Later, it calls that function with the dimension (also passing back the element, must for reference).
  5. I removed the bit watching the element itself; it shouldn't be necessary (unless the element is an image). But I added watching for images within it — specifically, the images that aren't (yet) loaded.
  6. There are two termination conditions: A) There were no images that we needed to wait for, and B) There were. So we need to do our done logic in two places, so I put it in a nested function (since one of those places is in a load event handler).
  7. Since one of our termination conditions is asynchronous, I made them both asynchronous so the caller sees the same thing (an asynchronous result) regardless of whether there were images to watch or not, by calling done via setTimeout in the case where we don't have to wait for anything.
  8. The above doesn't watch for image load errors (via error), which it probably should; if an image load fails, it'll never issue the callback.

You may find this other SO answer about watching for image loads helpful.

like image 138
T.J. Crowder Avatar answered Oct 05 '22 02:10

T.J. Crowder