I'm trying to create something like a dynamic page, but I have this problem. When I fire history.pushState it creates large amount of history entries, even though the action that fires it is run only once. My code is as follows:
var url = 'http://localhost:8888/depeche-mode/violator'; // example url
var plainUrl = url + '/?plain',
startUrl = 'http://localhost:8888/',
newUrl = url.replace(startUrl, '#/');
$('#content').animate({
opacity: 0
}, 250, function() {
$('#content').load(plainUrl +' #content > *', function(response) {
$('#content').animate({opacity:1}, 250, function() {
document.title = pageTitle;
Posts.historyHash(newUrl);
});
});
});
edit:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
$(window).bind('hashchange', function() {
var url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',newUrl);
});
}
}
The problem is very serious when I want to use Back button in my browser - I need to click it couple of times before I actually get to change the url. What can I do?
You're calling historyHash when you load content, and historyHash registers an event handler on window — every time you call it. So you end up with a bunch of event handlers for the hashchange event.
You presumably only want one. Either just register one, or unregister the previous ones when registering a new one.
I can't quite tell which of those you want, but as the handler uses the newUrl, it may well be that you want to unregister previous handlers. If so, probably best to use an event namespace so you only unregister your own handlers:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
$(window)
.unbind('hashchange.historyhash') // Out with the old
.bind('hashchange.historyhash', function() { // In with the new
var url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',newUrl);
});
}
}
Although looking at it, you could just use a single handler and remember newUrl on Posts:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
this.newUrl = newUrl;
}
};
$(window).bind('hashchange', function() {
var url, nohash, properUrl;
if (Posts.newUrl) {
url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',Posts.newUrl);
}
});
How do you definitively detect whether or not the user has pressed the back button in the browser?
How do you enforce the use of an in-page back button inside a single page web application using a #URL system?
Why on earth don't browser back buttons fire their own events!?
(Note: As per Sharky's feedback, I've included code to detect backspaces)
So, I've seen these questions frequently on SO, and have recently run into the issue of controlling back button functionality myself. After a few days of searching for the best solution for my application (Single-Page with Hash Navigation), I've come up with a simple, cross-browser, library-less system for detecting the back button.
Most people recommend using:
window.onhashchange = function() {
//blah blah blah
}
However, this function will also be called when a user uses on in-page element that changes the location hash. Not the best user experience when your user clicks and the page goes backwards or forwards.
To give you a general outline of my system, I'm filling up an array with previous hashes as my user moves through the interface. It looks something like this:
function updateHistory(curr) {
window.location.lasthash.push(window.location.hash);
window.location.hash = curr;
}
Pretty straight forward. I do this to ensure cross-browser support, as well as support for older browsers. Simply pass the new hash to the function, and it'll store it for you and then change the hash (which is then put into the browser's history).
I also utilise an in-page back button that moves the user between pages using the lasthash array. It looks like this:
function goBack() {
window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
//blah blah blah
window.location.lasthash.pop();
}
So this will move the user back to the last hash, and remove that last hash from the array (I have no forward button right now).
So. How do I detect whether or not a user has used my in-page back button, or the browser button?
At first I looked at window.onbeforeunload, but to no avail - that is only called if the user is going to change pages. This does not happen in a single-page-application using hash navigation.
So, after some more digging, I saw recommendations for trying to set a flag variable. The issue with this in my case, is that I would try to set it, but as everything is asynchronous, it wouldn't always be set in time for the if statement in the hash change. .onMouseDown wasn't always called in click, and adding it to an onclick wouldn't ever trigger it fast enough.
This is when I started to look at the difference between document, and window. My final solution was to set the flag using document.onmouseover, and disable it using document.onmouseleave.
What happens is that while the user's mouse is inside the document area (read: the rendered page, but excluding the browser frame), my boolean is set to true. As soon as the mouse leaves the document area, the boolean flips to false.
This way, I can change my window.onhashchange to:
window.onhashchange = function() {
if (window.innerDocClick) {
window.innerDocClick = false;
} else {
if (window.location.hash != '#undefined') {
goBack();
} else {
history.pushState("", document.title, window.location.pathname);
location.reload();
}
}
}
You'll note the check for #undefined. This is because if there is no history available in my array, it returns undefined. I use this to ask the user if they want to leave using a window.onbeforeunload event.
So, in short, and for people that aren't necessarily using an in-page back button or an array to store the history:
document.onmouseover = function() {
//User's mouse is inside the page.
window.innerDocClick = true;
}
document.onmouseleave = function() {
//User's mouse has left the page.
window.innerDocClick = false;
}
window.onhashchange = function() {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back button was clicked
}
}
And there you have it. a simple, three-part way to detect back button usage vs in-page elements with regards to hash navigation.
EDIT:
To ensure that the user doesn't use backspace to trigger the back event, you can also include the following (Thanks to #thetoolman on this Question):
$(function(){
/*
* this swallows backspace keys on any non-input element.
* stops backspace -> back
*/
var rx = /INPUT|SELECT|TEXTAREA/i;
$(document).bind("keydown keypress", function(e){
if( e.which == 8 ){ // 8 == backspace
if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
e.preventDefault();
}
}
});
});
You can try popstate event handler, e.g:
window.addEventListener('popstate', function(event) {
// The popstate event is fired each time when the current history entry changes.
var r = confirm("You pressed a Back button! Are you sure?!");
if (r == true) {
// Call Back button programmatically as per user confirmation.
history.back();
// Uncomment below line to redirect to the previous page instead.
// window.location = document.referrer // Note: IE11 is not supporting this.
} else {
// Stay on the current page.
history.pushState(null, null, window.location.pathname);
}
history.pushState(null, null, window.location.pathname);
}, false);
Note: For the best results, you should load this code only on specific pages where you want to implement the logic to avoid any other unexpected issues.
The popstate event is fired each time when the current history entry changes (user navigates to a new state). That happens when user clicks on browser's Back/Forward buttons or when history.back(), history.forward(), history.go() methods are programatically called.
The event.state is property of the event is equal to the history state object.
For jQuery syntax, wrap it around (to add even listener after document is ready):
(function($) {
// Above code here.
})(jQuery);
See also: window.onpopstate on page load
See also the examples on Single-Page Apps and HTML5 pushState page:
<script>
// jQuery
$(window).on('popstate', function (e) {
var state = e.originalEvent.state;
if (state !== null) {
//load content with ajax
}
});
// Vanilla javascript
window.addEventListener('popstate', function (e) {
var state = e.state;
if (state !== null) {
//load content with ajax
}
});
</script>
This should be compatible with Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ and similar.
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
alert('hello world');
}
This is the only one solution that worked for me (it's not a onepage website).
It's working with Chrome, Firefox and Safari.
I had been struggling with this requirement for quite a while and took some of the solutions above to implement it. However, I stumbled upon an observation and it seems to work across Chrome, Firefox and Safari browsers + Android and iPhone
On page load:
window.history.pushState({page: 1}, "", "");
window.onpopstate = function(event) {
// "event" object seems to contain value only when the back button is clicked
// and if the pop state event fires due to clicks on a button
// or a link it comes up as "undefined"
if(event){
// Code to handle back button or prevent from navigation
}
else{
// Continue user action through link or button
}
}
Let me know if this helps. If am missing something, I will be happy to understand.
In javascript, navigation type 2 means browser's back or forward button clicked and the browser is actually taking content from cache.
if(performance.navigation.type == 2)
{
//Do your code here
}
Correct answer is already there to answer the question. I want to mention new JavaScript API PerformanceNavigationTiming, it's replacing deprecated performance.navigation.
Following code will log in console "back_forward" if user landed on your page using back or forward button. Take a look at compatibility table before using it in your project.
var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
console.log(perfEntries[i].type);
}
This will definitely work (For detecting back button click)
$(window).on('popstate', function(event) {
alert("pop");
});
My variant:
const inFromBack = performance && performance.getEntriesByType( 'navigation' ).map( nav => nav.type ).includes( 'back_forward' )
Browser: https://jsfiddle.net/Limitlessisa/axt1Lqoz/
For mobile control: https://jsfiddle.net/Limitlessisa/axt1Lqoz/show/
$(document).ready(function() {
$('body').on('click touch', '#share', function(e) {
$('.share').fadeIn();
});
});
// geri butonunu yakalama
window.onhashchange = function(e) {
var oldURL = e.oldURL.split('#')[1];
var newURL = e.newURL.split('#')[1];
if (oldURL == 'share') {
$('.share').fadeOut();
e.preventDefault();
return false;
}
//console.log('old:'+oldURL+' new:'+newURL);
}
.share{position:fixed; display:none; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.8); color:white; padding:20px;
<!DOCTYPE html>
<html>
<head>
<title>Back Button Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body style="text-align:center; padding:0;">
Share
<div class="share" style="">
<h1>Test Page</h1>
<p> Back button press please for control.</p>
</div>
</body>
</html>
See this:
history.pushState(null, null, location.href);
window.onpopstate = function () {
history.go(1);
};
it works fine...
I was able to use some of the answers in this thread and others to get it working in IE and Chrome/Edge. history.pushState for me wasn't supported in IE11.
if (history.pushState) {
//Chrome and modern browsers
history.pushState(null, document.title, location.href);
window.addEventListener('popstate', function (event) {
history.pushState(null, document.title, location.href);
});
}
else {
//IE
history.forward();
}
A full-fledged component can be implemented only if you redefine the API (change the methods of object ' history ')
I will share the class just written.
Tested on Chrome and Mozilla
Support only HTML5 and ECMAScript5-6
class HistoryNavigation {
static init()
{
if(HistoryNavigation.is_init===true){
return;
}
HistoryNavigation.is_init=true;
let history_stack=[];
let n=0;
let current_state={timestamp:Date.now()+n};
n++;
let init_HNState;
if(history.state!==null){
current_state=history.state.HNState;
history_stack=history.state.HNState.history_stack;
init_HNState=history.state.HNState;
} else {
init_HNState={timestamp:current_state.timestamp,history_stack};
}
let listenerPushState=function(params){
params=Object.assign({state:null},params);
params.state=params.state!==null?Object.assign({},params.state):{};
let h_state={ timestamp:Date.now()+n};
n++;
let key = history_stack.indexOf(current_state.timestamp);
key=key+1;
history_stack.splice(key);
history_stack.push(h_state.timestamp);
h_state.history_stack=history_stack;
params.state.HNState=h_state;
current_state=h_state;
return params;
};
let listenerReplaceState=function(params){
params=Object.assign({state:null},params);
params.state=params.state!==null?Object.assign({},params.state):null;
let h_state=Object.assign({},current_state);
h_state.history_stack=history_stack;
params.state.HNState=h_state;
return params;
};
let desc=Object.getOwnPropertyDescriptors(History.prototype);
delete desc.constructor;
Object.defineProperties(History.prototype,{
replaceState:Object.assign({},desc.replaceState,{
value:function(state,title,url){
let params={state,title,url};
HistoryNavigation.dispatchEvent('history.state.replace',params);
params=Object.assign({state,title,url},params);
params=listenerReplaceState(params);
desc.replaceState.value.call(this,params.state,params.title,params.url);
}
}),
pushState:Object.assign({},desc.pushState,{
value:function(state,title,url){
let params={state,title,url};
HistoryNavigation.dispatchEvent('history.state.push',params);
params=Object.assign({state,title,url},params);
params=listenerPushState(params);
return desc.pushState.value.call(this, params.state, params.title, params.url);
}
})
});
HistoryNavigation.addEventListener('popstate',function(event){
let HNState;
if(event.state==null){
HNState=init_HNState;
} else {
HNState=event.state.HNState;
}
let key_prev=history_stack.indexOf(current_state.timestamp);
let key_state=history_stack.indexOf(HNState.timestamp);
let delta=key_state-key_prev;
let params={delta,event,state:Object.assign({},event.state)};
delete params.state.HNState;
HNState.history_stack=history_stack;
if(event.state!==null){
event.state.HNState=HNState;
}
current_state=HNState;
HistoryNavigation.dispatchEvent('history.go',params);
});
}
static addEventListener(...arg)
{
window.addEventListener(...arg);
}
static removeEventListener(...arg)
{
window.removeEventListener(...arg);
}
static dispatchEvent(event,params)
{
if(!(event instanceof Event)){
event=new Event(event,{cancelable:true});
}
event.params=params;
window.dispatchEvent(event);
};
}
HistoryNavigation.init();
// exemple
HistoryNavigation.addEventListener('popstate',function(event){
console.log('Will not start because they blocked the work');
});
HistoryNavigation.addEventListener('history.go',function(event){
event.params.event.stopImmediatePropagation();// blocked popstate listeners
console.log(event.params);
// back or forward - see event.params.delta
});
HistoryNavigation.addEventListener('history.state.push',function(event){
console.log(event);
});
HistoryNavigation.addEventListener('history.state.replace',function(event){
console.log(event);
});
history.pushState({h:'hello'},'','');
history.pushState({h:'hello2'},'','');
history.pushState({h:'hello3'},'','');
history.back();
```
Here's my take at it. The assumption is, when the URL changes but there has no click within the document detected, it's a browser back (yes, or forward). A users click is reset after 2 seconds to make this work on pages that load content via Ajax:
(function(window, $) {
var anyClick, consoleLog, debug, delay;
delay = function(sec, func) {
return setTimeout(func, sec * 1000);
};
debug = true;
anyClick = false;
consoleLog = function(type, message) {
if (debug) {
return console[type](message);
}
};
$(window.document).click(function() {
anyClick = true;
consoleLog("info", "clicked");
return delay(2, function() {
consoleLog("info", "reset click state");
return anyClick = false;
});
});
return window.addEventListener("popstate", function(e) {
if (anyClick !== true) {
consoleLog("info", "Back clicked");
return window.dataLayer.push({
event: 'analyticsEvent',
eventCategory: 'test',
eventAction: 'test'
});
}
});
})(window, jQuery);
The document.mouseover does not work for IE and FireFox.
However I have tried this :
$(document).ready(function () {
setInterval(function () {
var $sample = $("body");
if ($sample.is(":hover")) {
window.innerDocClick = true;
} else {
window.innerDocClick = false;
}
});
});
window.onhashchange = function () {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back or forward button was pressed
}
};
This works for Chrome and IE and not FireFox. Still working to get FireFox right. Any easy way on detecting Browser back/forward button click are welcome, not particularly in JQuery but also AngularJS or plain Javascript.
I solved it by keeping track of the original event that triggered the hashchange (be it a swipe, a click or a wheel), so that the event wouldn't be mistaken for a simple landing-on-page, and using an additional flag in each of my event bindings. The browser won't set the flag again to false when hitting the back button:
var evt = null,
canGoBackToThePast = true;
$('#next-slide').on('click touch', function(e) {
evt = e;
canGobackToThePast = false;
// your logic (remember to set the 'canGoBackToThePast' flag back to 'true' at the end of it)
}
<input style="display:none" id="__pageLoaded" value=""/>
$(document).ready(function () {
if ($("#__pageLoaded").val() != 1) {
$("#__pageLoaded").val(1);
} else {
shared.isBackLoad = true;
$("#__pageLoaded").val(1);
// Call any function that handles your back event
}
});
The above code worked for me. On mobile browsers, when the user clicked on the back button, we wanted to restore the page state as per his previous visit.
Solution for Kotlin/JS (React):
import org.w3c.dom.events.Event
import kotlin.browser.document
import kotlin.browser.window
...
override fun componentDidMount() {
window.history.pushState(null, document.title, window.location.href)
window.addEventListener("popstate", actionHandler)
}
...
val actionHandler: (Event?) -> Unit = {
window.history.pushState(
null,
document.title,
window.location.href
)
// add your actions here
}
Was looking for a solution for this issue and put together a simple skeleton test html based on a few answers here and the MDN Web Doc pages for History.pushState() and WindowEventHandlers.onpopstate.
The following HTML and JavaScript is easy enough to copy and paste and test.
Works with back and forward browser buttons, shortcut keys, adds a change to the URL (which is important in some cases).
Simply enough to add to existing code key points and should be expandable too.
<html>
<body>
<div id="p1">Option 1</div>
<div id="p2">Option 2</div>
<div id="p3">Option 3</div>
<div id="p4">Option 4</div>
<div id="c"></div>
<script>
var chg={
set:function(str){
var d=document.getElementById("c");
d.textContent=str;
},
go:function(e){
var s={"p":this.id};
chg.set(s.p);
hstry.add(s);
}
};
var hstry={
add:function(s){
var u=new URL(window.location);
u.searchParams.set("x",s.p);
window.history.pushState(s,"",u);
},
adjust:function(state){
if(state.p){
chg.set(state.p);
}
}
};
window.onpopstate=function(e){
console.log("popstate, e.state:["+ JSON.stringify(e.state) +"]");
hstry.adjust(e.state);
}
window.onload=function(){
var i,d,a=["p1","p2","p3","p4"];
for(i=0;i<a.length;i++){
d=document.getElementById(a[i]);
d.addEventListener("click",chg.go,false);
}
}
</script>
</body>
</html>
browser will emit popstate event if you navigate through your app with calling
window.history.pushState({},'','/to')
If you manually enter the addresses into the address bar and click on the back button, popstate event will NOT be fired.
If you navigate in your app with this simplified function
const navigate = (to) => {
window.history.pushState({}, ",", to);
};
then this will work
const handlePopstate = () => {
console.log("popped");
};
window.addEventListener("popstate", handlePopstate);
I tried the above options but none of them is working for me. Here is the solution
if(window.event)
{
if(window.event.clientX < 40 && window.event.clientY < 0)
{
alert("Browser back button is clicked...");
}
else
{
alert("Browser refresh button is clicked...");
}
}
Refer this link http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli for more details
Could someone please share experience / code how we can detect the browser back button click (for any type of browsers)?
We need to cater all browser that doesn't support HTML5
The 'popstate' event only works when you push something before. So you have to do something like this:
jQuery(document).ready(function($) {
if (window.history && window.history.pushState) {
window.history.pushState('forward', null, './#forward');
$(window).on('popstate', function() {
alert('Back button was pressed.');
});
}
});
For browser backward compatibility I recommend: history.js
In javascript, navigation type 2 means browser's back or forward button clicked and the browser is actually taking content from cache.
if(performance.navigation.type == 2) {
//Do your code here
}
there are a lot of ways how you can detect if user has clicked on the Back button. But everything depends on what your needs. Try to explore links below, they should help you.
Detect if user pressed "Back" button on current page:
Is there a way using Jquery to detect the back button being pressed cross browsers
detect back button click in browser
Detect if current page is visited after pressing "Back" button on previous("Forward") page:
Is there a cross-browser onload event when clicking the back button?
trigger event on browser back button click
Found this to work well cross browser and mobile back_button_override.js .
(Added a timer for safari 5.0)
// managage back button click (and backspace)
var count = 0; // needed for safari
window.onload = function () {
if (typeof history.pushState === "function") {
history.pushState("back", null, null);
window.onpopstate = function () {
history.pushState('back', null, null);
if(count == 1){window.location = 'your url';}
};
}
}
setTimeout(function(){count = 1;},200);
In case of HTML5 this will do the trick
window.onpopstate = function() {
alert("clicked back button");
}; history.pushState({}, '');
You can use this awesome plugin
https://github.com/ianrogren/jquery-backDetect
All you need to do is to write this code
$(window).load(function(){
$('body').backDetect(function(){
// Callback function
alert("Look forward to the future, not the past!");
});
});
Best
In my case I am using jQuery .load() to update DIVs in a SPA (single page [web] app) .
Being new to working with $(window).on('hashchange', ..) event listener , this one proved challenging and took a bit to hack on. Thanks to reading a lot of answers and trying different variations, finally figured out how to make it work in the following manner. Far as I can tell, it is looking stable so far.
In summary - there is the variable globalCurrentHash that should be set each time you load a view.
Then when $(window).on('hashchange', ..) event listener runs, it checks the following:
If location.hash has the same value, it means Going Forward
If location.hash has different value, it means Going Back
I realize using global vars isn't the most elegant solution, but doing things OO in JS seems tricky to me so far. Suggestions for improvement/refinement certainly appreciated
Set Up:
Define a global var :
var globalCurrentHash = null;
When calling .load() to update the DIV, update the global var as well :
function loadMenuSelection(hrefVal) {
$('#layout_main').load(nextView);
globalCurrentHash = hrefVal;
}
On page ready, set up the listener to check the global var to see if Back Button is being pressed:
$(document).ready(function(){
$(window).on('hashchange', function(){
console.log( 'location.hash: ' + location.hash );
console.log( 'globalCurrentHash: ' + globalCurrentHash );
if (location.hash == globalCurrentHash) {
console.log( 'Going fwd' );
}
else {
console.log( 'Going Back' );
loadMenuSelection(location.hash);
}
});
});
It's available in the HTML5 History API. The event is called 'popstate'
Disable the url button by following function
window.onload = function () {
if (typeof history.pushState === "function") {
history.pushState("jibberish", null, null);
window.onpopstate = function () {
history.pushState('newjibberish', null, null);
// Handle the back (or forward) buttons here
// Will NOT handle refresh, use onbeforeunload for this.
};
}
else {
var ignoreHashChange = true;
window.onhashchange = function () {
if (!ignoreHashChange) {
ignoreHashChange = true;
window.location.hash = Math.random();
// Detect and redirect change here
// Works in older FF and IE9
// * it does mess with your hash symbol (anchor?) pound sign
// delimiter on the end of the URL
}
else {
ignoreHashChange = false;
}
};
}
};
Hasan Badshah's answer worked for me, but the method is slated to be deprecated and may be problematic for others going forward. Following the MDN web docs on alternative methods, I landed here: PerformanceNavigationTiming.type
if (performance.getEntriesByType("navigation")[0].type === 'back_forward') {
// back or forward button functionality
}
This doesn't directly solve for back button over the forward button, but was good enough for what I needed. In the docs they detail the available event data that may be helpful with solving your specific needs:
function print_nav_timing_data() {
// Use getEntriesByType() to just get the "navigation" events
var perfEntries = performance.getEntriesByType("navigation");
for (var i=0; i < perfEntries.length; i++) {
console.log("= Navigation entry[" + i + "]");
var p = perfEntries[i];
// dom Properties
console.log("DOM content loaded = " + (p.domContentLoadedEventEnd -
p.domContentLoadedEventStart));
console.log("DOM complete = " + p.domComplete);
console.log("DOM interactive = " + p.interactive);
// document load and unload time
console.log("document load = " + (p.loadEventEnd - p.loadEventStart));
console.log("document unload = " + (p.unloadEventEnd -
p.unloadEventStart));
// other properties
console.log("type = " + p.type);
console.log("redirectCount = " + p.redirectCount);
}
}
According to the Docs at the time of this post it is still in a working draft state and is not supported in IE or Safari, but that may change by the time it is finished. Check the Docs for updates.
suppose you have a button:
<button onclick="backBtn();">Back...</button>
Here the code of the backBtn method:
function backBtn(){
parent.history.back();
return false;
}
What's the correct way of detecting when an iframe gets or loses focus (i.e. will or will not receive keyboard events)? The following is not working in Fx4:
var iframe = /* my iframe */;
iframe.addEventListener("focus", function() { /* never gets called */ }, false);
You can poll "document.activeElement" to determine if it matches the iframe. Polling isn't ideal, but it works:
function checkFocus() {
if(document.activeElement == document.getElementsByTagName("iframe")[0]) {
console.log('iframe has focus');
} else {
console.log('iframe not focused');
}
}
window.setInterval(checkFocus, 1000);
i know it's old, but i also had the same problem.
i ended up using this little pice of code:
$(document).on('focusout', function(){
setTimeout(function(){
// using the 'setTimout' to let the event pass the run loop
if (document.activeElement instanceof HTMLIFrameElement) {
// Do your logic here..
}
},0);
});
Turns out it's not really possible. I had to change the logic of my page to avoid the need of tracking if the iframe has focus.
How to check when an iframe has been clicked in or out of as well as hover-state.
Note: I would highly recommend you don't choose a polling method and go with an event driven method such as this.
Disclaimer
It is not possible to use the focus or blur events directly on an iframe but you can use them on the window to provide an event driven method of checking the document.activeElement. Thus you can accomplish what you're after.
Although we're now in 2018, my code is being implemented in GTM and tries to be cross browser compatible back to IE 11. This means there's more efficient code if you're utilizing newer ES/ECMAScript features.
Setup
I'm going to take this a few steps further to show that we can also get the iframe's src attribute as well as determine if it's being hovered.
Code
You would ideally need to put this in a document ready event, or at least encapsulate it so that the variables aren't global [maybe use an IIFE]. I did not wrap it in a document ready because it's handled by GTM. It may also depend where you place this or how you're loading it such as in the footer.
https://jsfiddle.net/9285tbsm/9/
I have noticed in the JSFiddle preview that it's already an iframe, sometimes you have to focus it first before events start to capture. Other issues can be that your browser window isn't yet focused either.
// Helpers
var iframeClickedLast;
function eventFromIframe(event) {
var el = event.target;
return el && el.tagName && el.tagName.toLowerCase() == 'iframe';
}
function getIframeSrc(event) {
var el = event.target;
return eventFromIframe(event) ? el.getAttribute('src') : '';
}
// Events
function windowBlurred(e) {
var el = document.activeElement;
if (el.tagName.toLowerCase() == 'iframe') {
console.log('Blurred: iframe CLICKED ON', 'SRC:', el.getAttribute('src'), e);
iframeClickedLast = true;
}
else {
console.log('Blurred', e);
}
}
function windowFocussed(e) {
if (iframeClickedLast) {
var el = document.activeElement;
iframeClickedLast = false;
console.log('Focussed: iframe CLICKED OFF', 'SRC:', el.getAttribute('src'), e);
}
else {
console.log('Focussed', e);
}
}
function iframeMouseOver(e) {
console.log('Mouse Over', 'SRC:', getIframeSrc(e), e);
}
function iframeMouseOut(e) {
console.log('Mouse Out', 'SRC:', getIframeSrc(e), e);
}
// Attach Events
window.addEventListener('focus', windowFocussed, true);
window.addEventListener('blur', windowBlurred, true);
var iframes = document.getElementsByTagName("iframe");
for (var i = 0; i < iframes.length; i++) {
iframes[i].addEventListener('mouseover', iframeMouseOver, true);
iframes[i].addEventListener('mouseout', iframeMouseOut, true);
}
I have solved this by using contentWindow instead of contentDocument.
The good thing about contentWindow is
it works also in case user clicks another window (another application) or another browser tab. If using activeElement, if user clicks away from the entire window to go to another application, then that logic still think the iframe is in focus, while it is not
and we don't need to poll and do a setInterval at all. This uses the normal addEventListener
let iframe = document.getElementsByTagName("iframe")[0];
// or whatever way you do to grab that iFrame, say you have an `id`, then it's even more precise
if(iframe){
iframeWindow = iframe.contentWindow;
iframeWindow.addEventListener('focus', handleIframeFocused);
iframeWindow.addEventListener('blur', handleIframeBlurred);
}
function handleIframeFocused(){
console.log('iframe focused');
// Additional logic that you need to implement here when focused
}
function handleIframeBlurred(){
console.log('iframe blurred');
// Additional logic that you need to implement here when blurred
}
This solution is working for me on both mobile and desktop:
;(function pollForIframe() {
var myIframe = document.querySelector('#my_iframe');
if (!myIframe) return setTimeout(pollForIframe, 50);
window.addEventListener('blur', function () {
if (document.activeElement == myIframe) {
console.log('myIframe clicked!');
}
});
})();
The solution is to inject a javascript event on the parent page like this :
var script = document.createElement('script');
script.type = 'text/javascript';
script.innerHTML =
"document.addEventListener('click', function()" +
"{ if(document.getElementById('iframe')) {" +
// What you want
"}});";
head.appendChild(script);
Here is the code to Detecting when an iframe gets or loses focus
// This code can be used to verify Iframe gets focus/loses.
function CheckFocus(){
if (document.activeElement.id == $(':focus').context.activeElement.id) {
// here do something
}
else{
//do something
}
}
A compact function that accepts callbacks you want to run when iframe gets or loses focus.
/* eslint-disable no-unused-vars */
export default function watchIframeFocus(onFocus, onBlur) {
let iframeClickedLast;
function windowBlurred(e) {
const el = document.activeElement;
if (el.tagName.toLowerCase() == 'iframe') {
iframeClickedLast = true;
onFocus();
}
}
function windowFocussed(e) {
if (iframeClickedLast) {
iframeClickedLast = false;
onBlur();
}
}
window.addEventListener('focus', windowFocussed, true);
window.addEventListener('blur', windowBlurred, true);
}
This might work
document.addEventListener('click', function(event) {
var frame= document.getElementById("yourFrameID");
var isClickInsideFrame = frame.contains(event.target);
if (!isClickInsideFrame ) {
//exec code
}
});