Last Updated: December 26, 2018
·
10.65K
· joeldbirch

Reset iOS Safari's emulated hover state

In its infinite wisdom, Apple created its own way of emulating hover events on touch devices. If iOS Safari determines that hovering an element would cause a child element to be shown, the browser will trigger the parent's :hover CSS styles upon first touch, and cancel any click events that may be caused by child elements. This causes the child element to appear.

Unfortunately, this non-standard hover state is only reset when another "clickable element" is clicked, which is a problem if the user follows a child link to another page while the pseudo-hover state is still in place.

When the browser's "back" button is used, the previous page is restored from cache ("bfcache" to be precise) exactly as it was when the page was exited. At this point, if the restored page contains an element with the emulated hover state still in effect, nothing can be done to reset it. Even if the "hovered" element's still-visible child element is hidden programatically, or if another clickable element receives focus, subsequent clicks on the parent element will behave as if the "first touch" behaviour has already occurred. This can be problematic.

For example, if this situation has been triggered for a top-level item of a Suckerfish-style menu, then the cache-restored page will show the submenu still open. If you manage to hide the submenu by clicking elsewhere, the parent item will no longer trigger the hover-emulation effect and its link will be followed straightaway. This renders the associated submenu inaccessible. There's only one way I have found to get around this: a proper page refresh. The downsides of always forcing a page refresh are significant, though, so be warned.

The code below detects iOS Safari, then detects whether the page was loaded from the bgcache and, if so, forces a refresh of the page. You completely lose the benefit of the bgcache, however. Using the iOS Safari's back button will force a (much slower) refresh. Even worse: a refresh will also occur when switching back from another tab or app, or even turning the screen back on after it has gone to sleep. Any unfinished work on the page would be lost.

With the dire warnings out of the way, here is the JavaScript code that forces a page refresh rather than a bfcache restore (uses jQuery's event attachment):

if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) {
    $(window).on('pageshow', function(e) {
        if (e.originalEvent.persisted) {
            window.location.reload();
        }
    });
}

2 Responses
Add your response

It would be great to have a publishing date on this post. Regards and congrats about the site!

over 1 year ago ·

Do you by any chance know how to accomplish the same thing but not from the "back" button but switching back from another application?

For example, my link brings the user to the App Store but upon switching back to Safari, the hover state is still initiated. Any thoughts?

over 1 year ago ·