Back Button Behavior on a Page With an iframe


I am developing a widget for websites. This widget lays inside an iframe in a website’s page. One of my users (which is a site owner) complained about a weird behavior of my widget. On pages where the widget was implemented, the browser’s back button didn’t work properly. Instead of navigating the user to the previous page on the website, the back button navigated the user to the previous page inside the iframe.

Let me show you an example. This demo page includes two pages. The first page contains nothing but a link to the second page. When clicking on the link, we are redirected to the second page that contains an iframe. At this point, looking on the browser’s history will show us only the first page as expected:

In order to demonstrate navigation inside an iframe, the iframe in the second page contains a page with an anchor. This anchor refers to a different page. A click on the anchor causes navigation inside the iframe, but also adds a new history entry of the second page:

Pressing back will not return us back to the first page. Instead, it will change the iframe’s page and this is not the desired behavior.
What we really want is the iframe’s navigation not to interfere the browser’s navigation. Pressing the back button should take us back to the first page and not to the previous iframe’s page.

It appears that any location change in the iframe is stored in the browser’s history.

Once the problem is understood, the solution is pretty simple. Whenever the user navigates inside the iframe, we don’t want to add a new entry to the history. In order to do that, I’d like to explain a bit about anchors.

How anchors work?

When an anchor is clicked, it navigates to the new page and the new location is added to the browser’s hitory. But, if the url is the same as the current url, no history entry is added and the anchor only performs a page refresh. It is easy to verify what I am saying here by creating a page that contains a link to itself. Clicking on this anchor only refreshes the page and no history entry is added.

history.replaceState() To The Rescue

Luckily HTML5 gave us a great API for controlling the history. window.history exposes useful methods that let us manipulate the contents of the history stack. Among these methods we can find the replaceState() method. history.replaceState() can modify the current history entry and associate it with the current document.
Assuming we have the following anchor inside an iframe’s page: <a href="iframe2.html">iframe page 2</a>. Clicking on it redirects us to page2.html and adds a new history entry for that page. If the current location, prior to the anchor’s action, is the same as the anchor’s url, then no new history entry will be added.
Let’s manipulate the history and set the current location to the anchor’s url before the anchor performs it’s action:

Prevent anchors to add history entry
var anchors = document.getElementsByTagName('a');
for (var i = 0; i < anchors.length; i++) {
    var anchor = anchors[i];
    anchor.addEventListener('click', function(event) {
        history.replaceState(null, null, anchor.href);
    }, false);
}

This script runs through all the page’s anchors and attaches a click event. Whenever the user clicks on an anchor, the current location is replaced with the anchor’s href. And here we prevented from another history entry to be added.
Keep in mind that this script has to run at the end of the page, after the DOM has loaded.
Here You can see the solution. Navigating inside the iframe doesn’t create history entry:

I spent a lot of time trying to understand the behavior of anchors and history and finding a solution for the back button issue. I hope you’ll find this explanation interesting and useful.
Thanks for reading,
NaorYe

  • I’m surprised that this works. Wonder if that’s a bug or a feature since the user is in fact navigating. Don’t find a description of this behaviour in any official docs. I thought only way would be Ajax + replaceState. Good find. ๐Ÿ™‚

  • Your code was very useful
    Thanks for sharing!

  • Matty

    Howdy, great article!

    I see below you mention that can be used on a non-ajax form. I try to use the action attribute, but the form history does not seem to rewrite and hitting ‘back’ in the browser reloads the pre-submitted state.

    Any ideas?

    Thanks in advance.

  • Ronen

    Great article.
    The solution is problematic for dynamic links which are created in the iframe after the script runs.
    You will have to run it again or even better, attach the event listener to a parent element and check for the target element like:

    document.body.addEventListener(‘click’, function(event) {
    if (event.target.nodeName === ‘A’ && event.target.href) {
    history.replaceState(null, null, event.target.href);
    }
    }, false);

    • Thanks, I absolutely agree with you. The main purpose of this article was to explain the browser behaviour and suggest a possible solution.
      Thanks for your comment!

      NaorYe

      • Shreesh Katyayan

        Or why not attach event listener to the popstate in the iframe?

  • Lucas P

    I’m pretty sure this code does not work correctly.

    JavaScript doesn’t have block scope, so that for loop isn’t closing over the value of `anchor` at each iteration.

    I’m pretty sure that this code causes every link on the page to direct to the last link’s `href` because by the time a given event listener actually fires, `anchor` will be bound to the final element in the `anchors` array.

    If you convert the collection to an array with Array.prototype.slice() you can use anchors.forEach() instead, which should do what you want.

  • Arnaldo

    if i submit a form in iframe ? how do i prevent history ?

  • โ–บ Evil E. โ—„

    naorye : could you please post a working example with a form submit inside an iframe ? I cannot seem to get it to work with forms… ๐Ÿ™

  • Nick

    Naorye,
    I believe i have a similar issue happening with my website. Would you be able to look at it for me and offer advice? My web company isn’t very helpful unfortunately.

  • RuฤŸu Niketzรถ

    Thats great idea. Thank you.

  • โ–บ Evil E. โ—„

    naorye : could you please post a working example with a form submit inside an iframe (with method=”post”) ? I cannot get the code to work with forms… ๐Ÿ™ ๐Ÿ™ ๐Ÿ™