Zach’s ugly mug (his face)

Zach Leatherman

Emulating onhashchange without setInterval

21 Aug 2008 Zach Leatherman 5 min read

There is one limitation that all of the major JavaScript browser history management plugins have to hack around: How to tell when there is a change to the location.hash? Sure, you can tell when you’re modifying the hash yourself, but what if the user hits the back/forward button?

YUI’s History component and Really Simple History both use setInterval with an internal variable to compare and find changes. But this isn’t really an optimal solution. As proper front end engineers, we should avoid timeouts as much as possible. Internet Explorer 8 will have an onhashchange event that clients will be able to subscribe to. That will be nice. But surely, a cross browser solution without the use of setInterval exists.

Look, a cross browser solution without the use of setInterval:

  1. On initialization, we load an iframe onto the page that is positioned absolutely at -500px,-500px so the user can’t see it. It is a skeleton page that only needs cross browser code to add an “onscroll” event, and to be able to calculate the scrolled position of the iframe itself. For my example, I use jQuery and the dimensions plugin to accomplish this, but it could easily be trimmed down to only the bare essentials (or ported to a different library).
  2. To add an AJAX history entry into the browser’s history under an assigned hash string, we first add a <a name="hashString">hashString</a> to the <body> tag of the iframe. Using css to increase the size of the a tag proportional to the iframe’s height, we can guarantee scrolling will happen.
  3. Then, we change the location.hash of the iframe to point to that <a> tag. This will scroll the iframe to the content, and create a new entry in the browser’s history object.
  4. Inside the iframe, we have our onscroll event that fires when the scrolling in the previous step took place. (Minor IE-related workaround: The browser’s history object is changed, but the hash property doesn’t when attempting to read it later. Instead, we find the <a> that matches up with the scrollY/pageOffsetY property inside of the iframe, and retrieve the matching hash from the <a> tag.)

The nice thing about this approach is that you don’t even need a history manager anymore. This little iframe will do all of your dirty work for you. And it will even maintain your history alongside any other iframe browsing on the page.

Advantages:

  • Can serve as back button support and full AJAX history manager.
  • Page Weight: the test page and iframe HTML files together weigh in at 2.76 KB. That includes the non-jQuery JavaScript needed to do everything.
  • Cross browser: Tested in FF3, IE7, IE6, Opera 9.5, (not Safari — see below)

Limitations:

  • No bookmarking support. We aren’t changing the top hash, we’re changing the iframe hash, so these aren’t bookmarkable.

Sample Example:

Update: Given time for more rigorous testing, it doesn’t look like Safari supports this approach. So, until WebKit fixes #9166, we’ll have to stick to timers in Safari. A more pragmatic programmer than I might hack around this approach by exploiting #19202, but that certainly wouldn’t be a long term solution. I’ve also updated the test page above, at Dean Edwards humble request, to support dynamic client size text size changes.

5 Comments

➡ Load Disqus to Leave a Comment ⬅

Zach Leatherman

22 Aug 2008 at 05:51PM

Look for a fix to the "resize text" problem later this evening. I'm going to wait until I'm not on the employer dime to work on that.

Zach,
I'm intrigued by this. So simple and elegant that it is kinda scary. I'd love to see continued development down this path to try and work around the limitations as I see them:
1. No bookmarking ability. That could get especially tricky in browsers that register events on hash change, like Firefox. Cause and effect get all messed up.
2. Safari. *sigh* A-grade browsers. Bah.
3. Client-side style sheets. Text size changes. Zoom changes.

I'm in the process of writing a history manager right now (timer-based, supports IE8's default event), so I'll stay tuned.

Hey Zach, me again. I wanted to point you in the direction of some of the work being done on this topic:
HTML 5 and History Management

Also, I alluded to doing some work myself, that is now up at:
JSSM

It is based on the approach taken by RSH, and the two will be merged to be the same library come the respective 1.0 versions to prevent history manager proliferation.

Also, I noted somewhere you discussed using jQuery's data method to store information. The reason history managers have adopted the form-based approach is because it allows us to restore the session state after leaving the site (relying upon browsers automatically repopulating the form fields). The jQuery data element is garbage collected once the current document element changes and so if you navigate away you lose all associated information.

Hi Zach
I really liked your approach here and have been using it successfully on a site for the past few months. I have noticed that it does not seem to work with IE8 (even in compatibility mode). The IFrame appears to be reset when navigating back through history. Do you have any thoughts on this? Thanks again for your great work on this. JC

Zach Leatherman

08 Jul 2009 at 11:43PM

This approach shouldn't be needed in IE8, as it has the onhashchange event. See the article for a link.

Let me know if you're seeing problems with it!

Thanks for the kind words,
Zach