Shorts. by Jeff Kreeftmeijer

Javascript: Scroll to anchors in iframes

For a project I'm working on, I needed to get cross-frame anchors work.

When clicking one of the anchor links in the iframe, I needed to change the hash in the URL of the parent page. Also, since the iframe doesn't have scroll bars, I needed the parent frame to scroll to an anchor in the iframe.

Like with automatically resizing iframes, the solution I found relies heavlily on sending messages between the iframe and the parent. So this will work cross-origin, but only if you can run javascript in both the frame and the parent window.

The iframe loads a page with a list of anchors, which look like this;

<a name="anchor-1" href="#anchor-1">Anchor #1</a>

When the frame has finished loading, it'll send a message to the parent. Also, it has some javascript that hijacks clicks to these anchors. Instead of the default action, it sends the anchor's name to the parent:

$(document).ready(function(){
  window.parent.postMessage("ready", "*")

  $('a').click(function(){
    event.preventDefault()
    window.parent.postMessage({"setAnchor": $(this).attr('name')}, "*")
  })
})

In the parent, there's an event listener that sets the url hash by appending the anchor to window.location when it gets a setAnchor message from the frame. After that happens, a hashchange event is triggered, which calls the sendHash function to post the anchor back to the iframe.

window.addEventListener('message', function(event) {
  if(event.data == 'ready') {
    sendHash()
  }

  if(anchor= event.data['setAnchor']) {
    window.location.href = '#' + anchor
  }

  if(offset = event.data['offset']) {
    window.scrollTo(0, $('iframe').offset().top + offset)
  }
})

sendHash = function(){
  hash = window.location.hash.substring(1)
  $('iframe')[0].contentWindow.postMessage({"findElement": hash}, '*')
}

$(window).on('hashchange', sendHash)

After receiving the anchor again in the iframe, it finds the element and sends its top offset back to the parent:

window.addEventListener('message', function(event) {
  if(anchor = event.data['findElement']) {
    element = $('[name="' + anchor + '"]')
    window.parent.postMessage({"offset": element.offset().top}, "*")
  }
})

The parent receives the offset message and scrolls to the right position:

window.addEventListener('message', function(event) {
  if(offset = event.data['offset']) {
    window.scrollTo(0, $('iframe').offset().top + offset)
  }
}

That's a lot of messages, but it's been working geat in my tests. Here's the isolated source. As always, please let me know if you find any issues or have an idea to make it better.