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