Как обнаружить событие кнопки «Назад» в браузере - Cross Browser


219

Как вы определите, действительно ли пользователь нажал кнопку «Назад» в браузере?

Как вы применяете использование кнопки возврата внутри страницы внутри одностраничного веб-приложения, использующего #URLсистему?

Почему же кнопки браузера не запускают свои собственные события !?


2
Я хочу, чтобы события навигации (назад / вперед) были добавлены когда-нибудь. До сих пор это то существо , в работах developer.mozilla.org/en-US/docs/Web/API/...
llaaalu

Ответы:


184

(Примечание: в соответствии с отзывами Шарки я включил код для обнаружения возврата)

Итак, я часто сталкивался с этими вопросами в SO, и недавно столкнулся с проблемой управления функциональностью кнопки возврата самостоятельно. После нескольких дней поиска лучшего решения для моего приложения (одностраничный с хэш-навигацией) я нашел простую, кросс-браузерную, безбиблиотечную систему для обнаружения кнопки «назад».

Большинство людей рекомендуют использовать:

window.onhashchange = function() {
 //blah blah blah
}

Однако эта функция также будет вызываться, когда пользователь использует элемент на странице, который изменяет хэш расположения. Не лучший пользовательский опыт, когда ваш пользователь нажимает и страница перемещается назад или вперед.

Чтобы дать вам общее представление о моей системе, я заполняю массив предыдущими хэшами, когда мой пользователь перемещается по интерфейсу. Это выглядит примерно так:

function updateHistory(curr) {
    window.location.lasthash.push(window.location.hash);
    window.location.hash = curr;
}

Довольно прямо вперед. Я делаю это для обеспечения кросс-браузерной поддержки, а также поддержки старых браузеров. Просто передайте новый хеш функции, и он сохранит его для вас, а затем изменит хеш (который затем заносится в историю браузера).

Я также использую кнопку возврата на странице, которая перемещает пользователя между страницами с помощью lasthashмассива. Это выглядит так:

function goBack() {
    window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
    //blah blah blah
    window.location.lasthash.pop();
}

Так что это переместит пользователя обратно к последнему хешу и удалит этот последний хеш из массива (у меня сейчас нет кнопки вперед).

Так. Как определить, использовал ли пользователь мою кнопку возврата на странице или кнопку браузера?

Сначала я посмотрел window.onbeforeunload, но безрезультатно - это вызывается, только если пользователь собирается менять страницы. Этого не происходит в одностраничном приложении, использующем хеш-навигацию.

Итак, после еще нескольких копаний я увидел рекомендации по установке переменной-флага. Проблема с этим в моем случае заключается в том, что я попытался бы установить его, но, поскольку все является асинхронным, он не всегда будет установлен вовремя для оператора if в изменении хеша. .onMouseDownне всегда вызывался в клике, и добавление его в onclick никогда не вызывало бы его достаточно быстро.

Это когда я начал смотреть на разницу между document, и window. Мое окончательное решение состояло в том, чтобы установить флаг, используя document.onmouseover, и отключить его, используя document.onmouseleave.

Что происходит, когда мышь пользователя находится внутри области документа (читай: отображаемая страница, но исключая фрейм браузера), мой логический параметр установлен на true. Как только мышь покидает область документа, логическое значение переходит наfalse .

Таким образом, я могу изменить свой window.onhashchangeна:

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();
        }
    }
}

Вы отметите чек на #undefined. Это потому, что если в моем массиве нет доступной истории, он возвращается undefined. Я использую это, чтобы спросить пользователя, хотят ли они уйти, используяwindow.onbeforeunload событие.

Итак, вкратце, и для людей, которые не обязательно используют кнопку возврата на странице или массив для хранения истории:

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
    }
}

И там у вас есть это. простой, состоящий из трех частей способ обнаружения использования кнопки «Назад» и элементов на странице в отношении хеш-навигации.

РЕДАКТИРОВАТЬ:

Чтобы убедиться, что пользователь не использует backspace для запуска события back, вы также можете добавить следующее (спасибо @thetoolman по этому вопросу ):

$(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();
            }
        }
    });
});

5
+1 хорошая идея, но я думаю, что это потерпит неудачу, если пользователь использует любую комбинацию клавиш для «назад» (клавиша возврата на Firefox), в то время как его мышь находится в окне браузера
Sharky

3
Подойдет - я в офисе прямо сейчас в любом случае :) (РЕДАКТИРОВАТЬ: Готово сейчас)
Xarus

7
Как насчет мобильных (например, Ipad)
Басарат

5
А как насчет событий с трекпада в MAC. Может ли это быть захвачено?
Сриганеш Наванитхакришнан

6
Как я могу различить кнопки вперед и назад?
Mr_Perfect

79

Вы можете попробовать popstateобработчик событий, например:

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);

Примечание. Для достижения наилучших результатов этот код следует загружать только на определенных страницах, где требуется реализовать логику, чтобы избежать любых других непредвиденных проблем.

Событие popstate запускается каждый раз, когда изменяется текущая запись истории (пользователь переходит в новое состояние). Это происходит , когда пользователь нажимает на кнопки Back / Forward браузера или когда history.back(), history.forward(),history.go() методы программно называется.

event.state is события равно объекту состояния истории.

Для синтаксиса jQuery оберните его (чтобы добавить даже слушателя после того, как документ будет готов):

(function($) {
  // Above code here.
})(jQuery);

Смотрите также: window.onpopstate при загрузке страницы


См. Также примеры на одностраничных приложениях и 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>

Это должно быть совместимо с Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ и аналогичными.


2
Кенорб, вы правы, что popstate работает, чтобы захватить изменение, но оно не делает различий между программным вызовом события и тем, когда пользователь нажимает кнопки браузера.
Xarus

1
Отличный пост на одностраничных приложениях и HTML5 pushState . Идеально подходит для современных «одностраничных макетов» или «одностраничных приложений». Спасибо за ссылку и ваш ответ!
lowtechsun

1
В современных браузерах e.state кажется всегда неопределенным
FAjir

Я предлагаю вам поместить свой код во фрагмент, чтобы вы могли запустить его прямо здесь.
FourCinnamon0

16

Я довольно долго боролся с этим требованием и использовал некоторые из приведенных выше решений для его реализации. Однако я наткнулся на наблюдение, и оно, похоже, работает в браузерах Chrome, Firefox и Safari + Android и iPhone.

На странице загрузки:

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
  }
}

Позвольте мне знать, если это помогает. Если что-то упустил, я буду рад понять.


1
Не правда. eventимеет значение даже для кнопки вперед
Даниэль Бировский Попески

14

В javascript, тип навигации 2означает, что браузер нажал кнопку «назад» или «вперед», и браузер фактически берет контент из кэша.

if(performance.navigation.type == 2)
{
    //Do your code here
}

1
Это не работает на одностраничном приложении, результат всегда равен 1 (что означает перезагрузку). Кроме того, это не имеет значения между кнопками назад и вперед, что очень прискорбно.
Папийон

«Более того, это не делает разницы между кнопками« назад »и« вперед », что очень прискорбно». Да, потому что всякий раз, когда данные выбираются из кэша, в этот раз значение performance.navigation.type равно 2
Хасан Бадшах

Это не гарантирует работу во всех браузерах! поэтому вы должны написать что-то вроде этого: if (window.performance) {// ваш код, связанный с производительностью}, также лучше использовать TYPE_BACK_FORWARD вместо 2 для большей читабельности.
blank94

7

Это определенно будет работать (для обнаружения нажатия кнопки назад)

$(window).on('popstate', function(event) {
 alert("pop");
});

6

Посмотри это:

history.pushState(null, null, location.href);
    window.onpopstate = function () {
        history.go(1);
    };

работает нормально ...


Не работает в Chrome 78.0.3904.70 (Официальная сборка) (64-разрядная
версия

Подтверждено, что работа над Brave v1.3.118 >> Chromium: 80.0.3987.116 (официальная сборка) (64-разрядная)
ZeroThe2nd

Не работает, если вы зашли на страницу с внешнего сайта.
Милан Влах

5
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
  alert('hello world');
}

Это единственное решение, которое сработало для меня (это не одностраничный сайт). Работает с Chrome, Firefox и Safari.


1
window.performance.navigation устарела. Вот документы developer.mozilla.org/en-US/docs/Web/API/Performance/navigation
llaaalu

Это сработало для меня на моей странице .cshtml. Успешно работал на Chrome и IE. Спасибо
Justjyde

5

Правильный ответ уже есть, чтобы ответить на вопрос. Я хочу упомянуть новый JavaScript API PerformanceNavigationTiming , он заменяет устаревший performance.navigation .

Следующий код будет входить в консоль "back_forward", если пользователь зашел на вашу страницу с помощью кнопки назад или вперед. Посмотрите на таблицу совместимости, прежде чем использовать ее в своем проекте.

var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
    console.log(perfEntries[i].type);
}

Это не способ узнать, нажата ли кнопка «Назад» на этой странице . Он сообщает, был ли он нажат на последней странице .
Сеф Рид

Но это был именно тот ответ, который я искал, поскольку проблема, которую я получаю, возникает после того, как я нажму кнопку «Назад», а не до того, что я могу встроить некоторый код ответа, который останавливает мои проблемы с дублирующимися запросами. Спасибо.
Андрей

1
Это может не ответить на вопрос, но именно то, что мне нужно! Хороший - я не знал об этом ...
Hogsmill

4

Браузер: https://jsfiddle.net/Limitlessisa/axt1Lqoz/

Для мобильного управления: 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;">
    <a href="#share" id="share">Share</a>
    <div class="share" style="">
        <h1>Test Page</h1>
        <p> Back button press please for control.</p>
    </div>
</body>

</html>


Это не отвечает на заданный вопрос. Речь идет об обнаружении использования кнопки возврата на странице по сравнению с кнопкой возврата браузера.
Xarus

2
Вопрос помечен javascript, а не jquery.
skwny

3

Вот мой взгляд на это. Предполагается, что при изменении URL-адреса, но при отсутствии documentобнаруженного клика , браузер возвращается (да или вперед). Клик пользователя сбрасывается через 2 секунды, чтобы это работало на страницах, которые загружают контент через 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);

2

Я смог использовать некоторые ответы в этой теме и другие, чтобы заставить его работать в IE и Chrome / Edge. history.pushState для меня не поддерживается в 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();
}

Только что попробовал это на Chrome 78.0.3904.70 (Официальная сборка) (64-битная версия), и это не сработало.
Джефф

2

Полноценный компонент может быть реализован, только если вы переопределите API (измените методы объекта 'history'), я поделюсь только что написанным классом. Протестировано на Chrome и Mozilla. Поддерживает только HTML5 и 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();

    ```

Хороший подход! Спасибо за добавление к этому (я все еще нахожу удивительным, что после стольких лет в этой ветке есть разговор)
Xarus

Похоже, это умный механизм отслеживания истории. Тем не менее, код не прокомментирован, поэтому за ним довольно трудно следовать. Если кто-то уходит с текущей страницы, код не обнаружит нажатие этой кнопки, что не отвечает на первоначальный вопрос «Как вы определите, нажал ли пользователь кнопку« Назад »в браузере?» Нажатие кнопок и отслеживание истории - две разные вещи, даже если одна может вызвать другую. Тот факт, что разговоры все еще продолжаются, указывает на серьезный недостаток в дизайне веб-браузера.
CubicleSoft

1

Document.mouseover не работает для IE и FireFox. Однако я попробовал это:

$(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
  }
};

Это работает для Chrome и IE, а не FireFox. Все еще работаю, чтобы получить право FireFox. Любой простой способ обнаружить нажатие кнопки «Назад / Вперед» в браузере приветствуется, не только в JQuery, но и в AngularJS или обычном Javascript.


1
 <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

        }
    });

Приведенный выше код работал для меня. В мобильных браузерах, когда пользователь нажимал кнопку «Назад», мы хотели восстановить состояние страницы в соответствии с его предыдущим посещением.


Вышеупомянутый код работал для меня, в мобильных браузерах, когда пользователи нажимали кнопку «назад», мы хотели восстановить состояние страницы в соответствии с его предыдущим посещением
Ashwin

0

Я решил эту проблему, отслеживая исходное событие, которое вызвало hashchange(будь то удар, щелчок или колесо), чтобы событие не было ошибочно принято за простую посадку на странице, и используя дополнительный флаг в каждое из моих событий привязки. Браузер не установит флаг снова falseпри нажатии кнопки «Назад»:

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)
}

-21

Я попробовал вышеупомянутые варианты, но ни один из них не работает для меня. Вот решение

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...");
        }
    }

Обратитесь по этой ссылке http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli для получения более подробной информации


8
@MunamYousuf Довольно очевидно, почему этот фрагмент кода не работает. Он отслеживает кнопку возврата браузера, но кнопка находится вне области просмотра, поэтому координаты clientX и clientY не должны быть доступны. Предполагая, что это работает, как насчет iOS? Кнопка «Назад» находится внизу ... Кроме того, то, как она написана, супер неэффективно, это условие для каждого отдельного события, вызванного ... Боже, это так плохо, что я собираюсь дать ему отрицательный голос.
Джеймс Вонг - Восстановить Монику

Это ужасно! зачем ты это делаешь? : / и это даже не сработает, как сказал Джеймс.
msqar
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.