Zach’s ugly mug (his face) Zach Leatherman

Emulating onhashchange without setInterval

August 21, 2008

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.


< Newer
Writing a Front End Engineer&#8217;s Resume (or CV)
Older >
Double Whammy: OpenID and Microformats

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at IndieWeb Avatar for https://cloudcannon.com/CloudCannon. He is the creator and maintainer of IndieWeb Avatar for https://www.11ty.devEleventy (11ty), an award-winning open source site generator. At one point he became entirely too fixated on web fonts. He has given 79 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »

5 Comments
  1. Zach Leatherman Disqus

    22 Aug 2008
    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.
  2. Nathan Hammond Disqus

    28 Aug 2008
    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.
  3. Nathan Hammond Disqus

    08 Oct 2008
    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 ManagementAlso, I alluded to doing some work myself, that is now up at:JSSMIt 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.
  4. James Close Disqus

    06 Jul 2009
    Hi ZachI 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
  5. Zach Leatherman Disqus

    09 Jul 2009
    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
Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)