Reputation: 731
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;
});
}
Upvotes: 8
Views: 5021
Reputation: 1074295
I think there are three unrelated issues:
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:
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.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.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
.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).loaded
.done
logic in two places, so I put it in a nested function (since one of those places is in a load
event handler).done
via setTimeout
in the case where we don't have to wait for anything.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.
Upvotes: 5
Reputation: 10103
First off, congratulations on a very elegant plugin!
Unfortunately, onload
only fires on:
<body>
<link>
and <script>
<frame>
, <frameset>
and <iframe>
<img>
If you could predict the image dimensions in advance, that would be ideal. Since your use case won't allow that, you could bind to the onload
event based on the content of the footnote:
var resize = function() {
// within this handler, `this` will refer to the img tag.
var container = $(this).closest('.sidenote');
// ... resize calculation ...
}
// bind images in the sidenote to the resize handler
$('img', sidenote).load(resize);
Upvotes: 0
Reputation: 1105
At which point do you start calculating the elements?
I would suggest waiting for the complete Page to load bevor you calculate anything.
You can use the Jquery function ready:
$(document).ready(function(){...});
More information here:
Upvotes: 0