Como detectar o evento Botão Voltar do navegador - Cross Browser

218

Como você detecta definitivamente se o usuário pressionou ou não o botão Voltar no navegador?

Como você reforça o uso de um botão voltar na página dentro de um aplicativo Web de página única usando um #URLsistema?

Por que diabos os botões de retorno do navegador não acionam seus próprios eventos !?

Xarus
fonte
2
Desejo que os eventos de navegação (para trás / para frente) sejam adicionados em algum momento. Até agora, este é o ser algo em obras developer.mozilla.org/en-US/docs/Web/API/...
llaaalu

Respostas:

184

(Observação: conforme o feedback de Sharky, incluímos código para detectar backspaces)

Então, eu já vi essas perguntas frequentemente no SO e recentemente me deparei com a questão de controlar a funcionalidade do botão Voltar. Depois de alguns dias procurando a melhor solução para meu aplicativo (página única com navegação Hash), criei um sistema simples, sem navegador, sem biblioteca para detectar o botão voltar.

A maioria das pessoas recomenda o uso de:

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

No entanto, essa função também será chamada quando um usuário usar no elemento in-page que altera o hash do local. Não é a melhor experiência do usuário quando o usuário clica e a página retrocede ou avança.

Para dar uma visão geral do meu sistema, estou preenchendo uma matriz com hashes anteriores à medida que meu usuário se move pela interface. Parece algo como isto:

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

Bem direto. Faço isso para garantir o suporte entre navegadores e para navegadores mais antigos. Simplesmente passe o novo hash para a função, que será armazenada para você e depois altere o hash (que é colocado no histórico do navegador).

Também utilizo um botão voltar na página que move o usuário entre as páginas usando a lasthashmatriz. Se parece com isso:

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

Portanto, isso fará com que o usuário volte ao último hash e remova esse último hash da matriz (não tenho botão de avanço no momento).

Assim. Como detecto se um usuário usou ou não o botão voltar da página ou o botão do navegador?

No começo, olhei window.onbeforeunload, mas sem sucesso - isso só é chamado se o usuário mudar de página. Isso não acontece em um aplicativo de página única usando a navegação por hash.

Então, depois de mais algumas pesquisas, vi recomendações para tentar definir uma variável de flag. O problema com isso no meu caso é que eu tentaria defini-lo, mas como tudo é assíncrono, nem sempre seria definido a tempo para a instrução if na alteração de hash. .onMouseDownnem sempre era chamado em clique, e adicioná-lo a um onclick nunca o acionava com rapidez suficiente.

Foi quando comecei a olhar para a diferença entre document, e window. Minha solução final foi definir o sinalizador usando document.onmouseovere desativá-lo usando document.onmouseleave.

O que acontece é que, enquanto o mouse do usuário está dentro da área do documento (leia-se: a página renderizada, mas excluindo o quadro do navegador), meu booleano está definido como true. Assim que o mouse sai da área do documento, o booleano muda para false.

Dessa forma, eu posso mudar meu window.onhashchangepara:

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

Você observará a verificação #undefined. Isso ocorre porque, se não houver histórico disponível na minha matriz, ele retornará undefined. Eu uso isso para perguntar ao usuário se ele deseja sair usando um window.onbeforeunloadevento.

Portanto, para resumir, e para pessoas que não estão necessariamente usando um botão voltar da página ou uma matriz para armazenar o histórico:

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

E aí está. uma maneira simples e em três partes de detectar o uso do botão voltar versus elementos da página no que diz respeito à navegação por hash.

EDITAR:

Para garantir que o usuário não use backspace para acionar o evento back, você também pode incluir o seguinte (obrigado a @thetoolman nesta pergunta ):

$(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();
            }
        }
    });
});
Xarus
fonte
5
+1 boa idéia, mas acho que este irá falhar se os usos do usuário quaisquer atalho de teclado é para "back" (tecla de retrocesso no firefox), enquanto o mouse é a janela do navegador dentro
Sharky
3
Vai fazer - eu estou no escritório agora de qualquer maneira :) (EDIT: Feito agora)
Xarus
7
E quanto móvel (por exemplo, ipad)
basarat
5
E os eventos de furto do trackpad no MAC. Isso poderia ser capturado também?
Sriganesh Navaneethakrishnan
6
Como posso diferenciar os botões avançar e retroceder?
21816 Mr_Perfect
79

Você pode tentar o popstatemanipulador de eventos, por exemplo:

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

Nota: Para obter os melhores resultados, você deve carregar esse código apenas em páginas específicas nas quais deseja implementar a lógica para evitar outros problemas inesperados.

O evento popstate é acionado sempre que a entrada do histórico atual é alterada (o usuário navega para um novo estado). Isso acontece quando o usuário clica em Voltar Avançar botões do navegador / ou quando history.back(), history.forward(), history.go()métodos são programaticamente chamado.

A event.statepropriedade is do evento é igual ao objeto de estado do histórico.

Para a sintaxe do jQuery, envolva-a (para adicionar um ouvinte uniforme depois que o documento estiver pronto):

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

Consulte também: window.onpopstate no carregamento da página


Veja também os exemplos na página pushState de aplicativos de página única e HTML5 :

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

Isso deve ser compatível com Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ e similares.

kenorb
fonte
2
Kenorb, você está certo de que o popstate trabalha para capturar a alteração, mas não diferencia entre chamar programaticamente o evento e quando o usuário clica nos botões do navegador.
Xarus
1
Ótima postagem nos aplicativos de página única e no pushState HTML5 . Perfeito para modernos "layouts de uma página" ou "aplicativos de página única". Obrigado pelo link e sua resposta!
Lowtechsun 28/05
1
e.state parecem sempre indefinido em navegadores modernos
FAjir
Sugiro que você coloque seu código em um trecho para poder executá-lo diretamente aqui.
FourCinnamon0
16

Eu estava lutando com esse requisito há um bom tempo e aproveitei algumas das soluções acima para implementá-lo. No entanto, deparei-me com uma observação e parece funcionar nos navegadores Chrome, Firefox e Safari + Android e iPhone

No carregamento da página:

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

Avise-me se isso ajudar. Se estiver faltando alguma coisa, ficarei feliz em entender.

Itzmeygu
fonte
1
Não é verdade. eventtem valor mesmo para o botão de avanço
Daniel Birowsky Popeski
14

Em javascript, tipo de navegação 2significa clicar no botão voltar ou avançar do navegador e o navegador está realmente retirando o conteúdo do cache.

if(performance.navigation.type == 2)
{
    //Do your code here
}
Hasan Badshah
fonte
1
Isso não funciona em um aplicativo de página única, o resultado é sempre 1 (o que significa recarregar). Além disso, não faz a diferença entre os botões voltar e avançar, o que é muito lamentável.
Papillon
"Além disso, não faz a diferença entre o botão voltar e encaminhar, o que é muito lamentável." Sim, porque sempre que os dados são buscados no cache, o desempenho.navigation.type é 2
Hasan Badshah
Não é garantido que funcione em todos os navegadores! então você deve escrever algo como isto: if (window.performance) {// seu código relacionado ao desempenho} também é melhor usar TYPE_BACK_FORWARD em vez de 2 para obter mais legibilidade.
precisa saber é o seguinte
7

Definitivamente funcionará (para detectar o clique no botão Voltar)

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

Veja isso:

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

Funciona bem...

Jorge Rocha
fonte
Não funciona no Chrome 78.0.3904.70 (Compilação oficial) (64 bits)
Jeffz 30/10
Confirmado o trabalho no Brave v1.3.118 >> Chromium: 80.0.3987.116 (Compilação oficial) (64 bits)
ZeroThe2nd
Não funciona se você veio à página de um site externo.
Milan Vlach
5
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
  alert('hello world');
}

Esta é a única solução que funcionou para mim (não é um site de uma página). Está trabalhando com Chrome, Firefox e Safari.

escanxr
fonte
1
window.performance.navigation foi preterido. Aqui estão os documentos developer.mozilla.org/en-US/docs/Web/API/Performance/navigation
llaaalu
Isso funcionou para mim na minha página .cshtml. Trabalhou com sucesso no Chrome e IE. Obrigado
Justjyde
5

A resposta correta já está lá para responder à pergunta. Quero mencionar a nova API JavaScript PerformanceNavigationTiming , que está substituindo o desempenho descontinuado . .

O código a seguir fará o login no console "back_forward" se o usuário chegar à sua página usando o botão voltar ou avançar. Dê uma olhada na tabela de compatibilidade antes de usá-la em seu projeto.

var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
    console.log(perfEntries[i].type);
}
llaaalu
fonte
Esta não é uma maneira de saber se um botão Voltar é clicado nesta página . Diz se foi clicado na última página .
Seph Reed
Mas foi a resposta real que eu estava procurando, pois o problema que eu recebi é depois que eu apertei o botão de voltar e não antes, o que significa que posso criar algum código de resposta que interrompa meus problemas de solicitações duplicadas. Obrigado.
Andrew
1
Isso pode não responder à pergunta, mas exatamente o que eu precisava! Bom - eu não sabia disso ...
Hogsmill 18/06
4

Navegador: https://jsfiddle.net/Limitlessisa/axt1Lqoz/

Para controle móvel: 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>

Isa sem limites
fonte
Isso não responde à pergunta. A questão é detectar o uso de um botão voltar na página versus o botão voltar nativo do navegador.
Xarus
2
A pergunta está com a tag javascript, não jquery.
skwny
3

Aqui está a minha opinião. A suposição é que, quando o URL muda, mas não há clique no documentdetectado, é um navegador de volta (sim ou encaminhamento). Um clique do usuário é redefinido após 2 segundos para fazer isso funcionar em páginas que carregam conteúdo 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);
TomTom101
fonte
2

Consegui usar algumas das respostas neste tópico e outras para fazê-lo funcionar no IE e no Chrome / Edge. history.pushState para mim não era suportado no 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();
}
Arsalan Siddiqui
fonte
Apenas tentei isso no Chrome 78.0.3904.70 (versão oficial) (64 bits) e não funcionou.
Jeffz
2

Um componente completo pode ser implementado apenas se você redefinir a API (alterar os métodos do objeto 'history'). Compartilharemos a classe que acabamos de escrever. Testado no Chrome e Mozilla Suporta apenas HTML5 e 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();

    ```
AlexeyP0708
fonte
Boa abordagem! Obrigado por adicionar a isso (eu ainda acho incrível que haja uma conversa sobre este tópico depois de tantos anos)
Xarus
Este parece ser um mecanismo inteligente de rastreamento de histórico. No entanto, o código não é comentado, por isso é muito difícil de seguir. Se alguém sair da página atual, o código não detectará o pressionamento do botão, o que não responde à pergunta original de "Como você detecta definitivamente se o usuário pressionou ou não o botão Voltar no navegador?" O pressionamento de botões e o rastreamento do histórico são duas coisas diferentes, embora um possa acionar o outro. O fato de ainda haver conversas indica uma falha importante no design do navegador da web.
CubicleSoft 27/04
1

O document.mouseover não funciona para o IE e o FireFox. No entanto, eu tentei isso:

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

Isso funciona para o Chrome e o IE e não o FireFox. Ainda trabalhando para acertar o FireFox. Qualquer maneira fácil de detectar o clique no botão voltar / avançar do navegador é bem-vinda, não particularmente no JQuery, mas também no AngularJS ou no Javascript simples.

Dhruv Gupta
fonte
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

        }
    });

O código acima funcionou para mim. Nos navegadores móveis, quando o usuário clicava no botão Voltar, queríamos restaurar o estado da página conforme sua visita anterior.

Ashwin
fonte
Acima de código funcionou para mim, em navegadores móveis, quando os usuários clicaram no botão voltar, queríamos restaurar o estado da página de acordo com sua visita anterior
Ashwin
0

Eu o resolvi acompanhando o evento original que acionava o hashchange(seja um toque, um clique ou uma roda), para que o evento não fosse confundido com uma simples aterrissagem na página e usando uma sinalização adicional em cada uma das minhas ligações de eventos. O navegador não definirá novamente o sinalizador ao falsepressionar o botão Voltar:

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

fonte
-21

Eu tentei as opções acima, mas nenhuma delas está funcionando para mim. Aqui está a solução

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

Consulte este link http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli para obter mais detalhes

Sures Ramar
fonte
8
@MunamYousuf É bastante óbvio por que esse pedaço de código realmente não funciona. Ele rastreia o botão voltar do navegador, mas o botão está fora da janela de visualização, portanto, as coordenadas clientX e clientY não devem ser obtidas. Supondo que funcione, e quanto ao iOS? O botão voltar está na parte inferior ... Além disso, o modo como está escrito é super ineficiente, é condicional para todos os eventos desencadeados ... Deus, é tão ruim que vou dar um voto negativo.
James Wong - Restabelece Monica
Isso é horrível! por que você faria isso? : / e nem vai funcionar como o James disse.
msqar