Como posso detectar se um usuário chegou a uma página usando o botão Voltar?

90

Esta questão é semelhante a Rastrear quando o usuário pressiona o botão Voltar no navegador , mas não é o mesmo ... Tenho uma solução e a estou postando aqui para referência e feedback. Se alguém tiver opções melhores, sou todo ouvidos!

A situação é que tenho uma página com uma "edição no local", a la flickr. Ou seja, há um DIV "clique aqui para adicionar uma descrição", que quando clicado se transforma em uma TEXTAREA com os botões Salvar e Cancelar. Clicar em Salvar envia os dados para o servidor para atualizar o banco de dados e coloca a nova descrição no DIV no lugar de TEXTAREA. Se a página for atualizada, a nova descrição será exibida no banco de dados com uma opção "clique para editar". Coisas razoavelmente normais da Web 2.0 atualmente.

O problema é que se:

  1. a página é carregada sem a descrição
  2. uma descrição é adicionada pelo usuário
  3. a página é navegada para fora clicando em um link
  4. o usuário clica no botão Voltar

Então, o que é exibido (do cache do navegador) é a versão da página sem o DIV modificado dinamicamente contendo a nova descrição.

Este é um problema bastante grande, pois o usuário presume que sua atualização foi perdida e não necessariamente entenderá que precisa atualizar a página para ver as mudanças.

Portanto, a pergunta é: como você pode sinalizar uma página como modificada após ser carregada e, em seguida, detectar quando o usuário "volta para ela" e forçar uma atualização nessa situação?

Tom
fonte
Como isso é diferente da pergunta que você citou?
innaM
a pergunta é semelhante, mas acho que o ambiente e, portanto, a resposta é diferente, posso estar errado. minha interpretação de qual pode ser um problema que seria resolvido com a outra solução é: o usuário clica em uma guia em uma página que é carregada por ajax, depois em outra guia e assim por diante. clicar no botão Voltar o levará de volta a uma página diferente, não à guia anterior. eles querem percorrer o "histórico do ajax" dentro do "histórico da página". pelo menos essa é minha impressão do que o Yahoo Browser History Manager deve fazer. eu estava atrás de algo um pouco mais básico.
Tom
A resposta aceita apresenta seu truque do iframe.
innaM

Respostas:

79

Use um formulário oculto. Os dados do formulário são preservados (normalmente) nos navegadores quando você recarrega ou clica no botão Voltar para retornar a uma página. O seguinte vai para sua página (provavelmente perto do final):

<form name="ignore_me">
    <input type="hidden" id="page_is_dirty" name="page_is_dirty" value="0" />
</form>

No seu javascript, você precisará do seguinte:

var dirty_bit = document.getElementById('page_is_dirty');
if (dirty_bit.value == '1') window.location.reload();
function mark_page_dirty() {
    dirty_bit.value = '1';
}

O js que fareja o formulário deve ser executado depois que o html é totalmente analisado, mas você pode colocar o formulário e o js embutidos no topo da página (segundo js) se a latência do usuário for uma preocupação séria.

Nick White
fonte
5
"Os dados do formulário são preservados (normalmente) nos navegadores" - você pode expandir isso? não é preservado em alguns navegadores? ou em algumas situações?
Tom,
4
Na verdade, deixe-me listar todas as maneiras pelas quais posso pensar que isso pode dar errado. (1) O Opera mantém o estado completo da página e não executa novamente o js. (2) Se suas configurações de cache forem ajustadas para forçar o carregamento da página do servidor no botão Voltar, os dados do formulário podem ser perdidos, o que é bom para os propósitos desta questão. (3) Os navegadores do telefone têm um cache muito menor e o navegador pode já ter retirado a página do cache (o que não é um problema para esses propósitos).
Nick White,
10
Em alguns navegadores, as tags ocultas não são preservadas. Portanto, você pode usar a tag de texto oculta em vez de usar a tag de entrada oculta. <input type = "text" value = "0" id = 'txt' name = 'txt' style = "display: none" />
sajith
2
Isso funcionou muito bem para mim. Tenho certeza de que há situações em que ele quebra, mas todas as soluções resolverão esse problema.
Joren,
Ele não está funcionando no iPhone com o navegador Safari, experimente, quando você clicar no botão Voltar, js não será chamado novamente.
woheras
52

Aqui está uma solução moderna muito fácil para esse velho problema.

if (window.performance && window.performance.navigation.type === window.performance.navigation.TYPE_BACK_FORWARD) {
    alert('Got here using the browser "Back" or "Forward" button.');
}

window.performance é atualmente suportado por todos os principais navegadores.

Bassem
fonte
Eu não tinha ouvido falar disso. Acabei de encontrar esta documentação ( developer.mozilla.org/en-US/docs/Web/API/Performance/navigation ) e gostaria de ouvi-lo elaborar (por exemplo, por que a necessidade de em window.performance && window.performance.navigation.typevez de apenas window.performance.navigation.TYPE_BACK_FORWARD?).
Ryan
5
window.performance.navigation.TYPE_BACK_FORWARDé uma constante que é sempre igual a 2. Portanto, é usada apenas para comparação.
Bassem
Performance.navigation é compatível com todos os dispositivos, exceto andorid e opera mini, mas sem problemas, é muito bom ( developer.mozilla.org/en-US/docs/Web/API/Performance/navigation )
Elbaz
1
Parece que performance.navigationestá obsoleto e é sugerido usar developer.mozilla.org/en-US/docs/Web/API/…
RubberDuckRabbit
1
O Firefox v70 oferecerá suporte a performance.navigation = 2para cliques no botão Voltar. Até a v70, ele retornava incorretamente 1.
Andy Mercer
13

Este artigo explica isso. Veja o código abaixo: http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/

<html>
    <head>
        <script>

            function pageShown(evt){
                if (evt.persisted) {
                    alert("pageshow event handler called.  The page was just restored from the Page Cache (eg. From the Back button.");
                } else {
                    alert("pageshow event handler called for the initial load.  This is the same as the load event.");
                }
            }

            function pageHidden(evt){
                if (evt.persisted) {
                    alert("pagehide event handler called.  The page was suspended and placed into the Page Cache.");
                } else {
                    alert("pagehide event handler called for page destruction.  This is the same as the unload event.");
                }
            }

            window.addEventListener("pageshow", pageShown, false);
            window.addEventListener("pagehide", pageHidden, false);

        </script>
    </head>
    <body>
        <a href="http://www.webkit.org/">Click for WebKit</a>
    </body>
</html>
Drew Baker
fonte
1
Esta é uma boa resposta, funciona no Chrome 40 / Firefox 35 mais recente. No entanto, o IE11 não oferece suporte para esses eventos.
Slava Abakumov
De acordo com caniuse.com/#feat=page-transition-events , os eventos de transição de página são, na verdade, suportados no IE11 + Como falamos, há 88% de cobertura global do recurso
Cristian
Para sua informação, o Chrome retorna event.persisted= false, mesmo ao pressionar o botão Voltar. Você deve usar window.performance.navigationou PerformanceNavigationTiming para Chrome.
Andy Mercer
4

Para navegadores modernos, este parece ser o caminho certo agora:

const perfEntries = performance.getEntriesByType('navigation');
if (perfEntries.length && perfEntries[0].type === 'back_forward') {
  console.log('User got here from Back or Forward button.');
}

Ainda é "Experimental" em janeiro de 2020, mas parece ter um bom suporte.

https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming

getup8
fonte
1
melhor usar === em vez de ==
Aboozar Rajabi
2

Conforme mencionado acima, eu encontrei uma solução e a estou postando aqui para referência e feedback.

O primeiro estágio da solução é adicionar o seguinte à página:

<!-- at the top of the content page -->
<IFRAME id="page_is_fresh" src="fresh.html" style="display:none;"></IFRAME>
<SCRIPT style="text/javascript">
  function reload_stale_page() { location.reload(); }
</SCRIPT>

O conteúdo de fresh.htmlnão é importante, então o seguinte deve ser suficiente:

<!-- fresh.html -->
<HTML><BODY></BODY></HTML>

Quando o código do lado do cliente atualiza a página, ele precisa sinalizar a modificação da seguinte maneira:

function trigger_reload_if_user_clicks_back_button()
{
  // "dis-arm" the reload stale page function so it doesn't fire  
  // until the page is reloaded from the browser's cache
  window.reload_stale_page = function(){};

  // change the IFRAME to point to a page that will reload the 
  // page when it loads
  document.getElementById("page_is_fresh").src = "stale.html";
}

stale.htmlfaz todo o trabalho: Quando é carregado, ele chamará a reload_stale_pagefunção que atualizará a página se necessário. Na primeira vez que é carregado (ou seja, após a modificação ser feita, a reload_stale_pagefunção não fará nada.)

<!-- stale.html -->
<HTML><BODY>
<SCRIPT type="text/javascript">window.parent.reload_stale_page();</SCRIPT>
</BODY></HTML>

Pelo meu teste (mínimo) neste estágio, isso parece funcionar como desejado. Eu esqueci alguma coisa?

Tom
fonte
Não que eu saiba, mas não testei totalmente. getElementById não é compatível com alguns navegadores mais antigos, mas é fácil trocar por jquery ou algo assim, se necessário.
Tom
2

Você pode resolvê-lo usando o evento onbeforeunload :

window.onbeforeunload = function () { }

Ter uma função de gerenciador de eventos vazio onbeforeunload significa que a página será reconstruída toda vez que for acessada. Javascripts serão executados novamente, scripts do lado do servidor serão executados novamente, a página será construída como se o usuário estivesse acessando-a pela primeira vez, mesmo se o usuário acessou a página apenas pressionando o botão Voltar ou Avançar .

Aqui está o código completo de um exemplo:

<html>
<head>
<title>onbeforeunload.html</title>
<script>
window.onbeforeunload = function () { }

function myfun()
{
   alert("The page has been refreshed.");
}
</script>

<body onload="myfun()">

Hello World!<br>

</body>
</html>

Experimente, navegue para fora desta página e volte usando os botões "Voltar" ou "Avançar" em seu navegador.

Funciona bem no IE, FF e Chrome.

Claro que você pode fazer o que quiser dentro da função myfun .

Mais informações: http://www.hunlock.com/blogs/Mastering_The_Back_Button_With_Javascript

Nes
fonte
Funciona no Safari para mim
Jakob
2

Você pode usar localStorage ou sessionStorage ( http://www.w3schools.com/html/html5_webstorage.asp ) para definir um sinalizador (em vez de usar um formulário oculto).

Rerezz
fonte
é uma boa solução alternativa, mas como você faria se ele quisesse detectar isso todas as vezes, quando eu abri e fechei o site, preciso detectar apenas quando abri e voltar antes de fechar
Elbaz
1

Usando esta página, especialmente incorporando o comentário de @sajith sobre a resposta de @Nick White e esta página: http://www.mrc-productivity.com/techblog/?p=1235

<form name="ignore_me" style="display:none">
    <input type="text" id="reloadValue" name="reloadValue" value="" />
</form>

$(function ()
{
    var date = new Date();
    var time = date.getTime();

    if ($("#reloadValue").val().length === 0)
    {
        $("#reloadValue").val(time);
    }
    else
    {
        $("#reloadValue").val("");
        window.location.reload();
    }
});
nmit026
fonte
1

Aqui está uma versão do jQuery. Já tive a necessidade de usá-lo algumas vezes devido à maneira como o Safari para desktop / celular lida com o cache quando um usuário pressiona o botão Voltar.

$(window).bind("pageshow", function(event) {
    if (event.originalEvent.persisted) {
        // Loading from cache
    }
});
Wes
fonte
Não parece funcionar no Chrome, tanto quanto posso dizer. Um console.log só é acionado na instrução else (adicionada para teste), independentemente de como eu acesso a página.
Lucas
0

Eu encontrei um problema semelhante hoje e resolvi com localStorage (aqui com um pouco de jQuery):

$(function() {

    if ($('#page1').length) {
        // this code must only run on page 1

        var checkSteps = function () {
            if (localStorage.getItem('steps') == 'step2') {
                // if we are here, we know that:
                // 1. the user is on page 1
                // 2. he has been on page 2
                // 3. this function is running, which means the user has submitted the form
                // 4. but steps == step2, which is impossible if the user has *just* submitted the form
                // therefore we know that he has come back, probably using the back button of his browser
                alert('oh hey, welcome back!');
            } else {
                setTimeout(checkSteps, 100);
            }
        };

        $('#form').on('submit', function (e) {
            e.preventDefault();
            localStorage.setItem('steps', 'step1'); // if "step1", then we know the user has submitted the form
            checkOrderSteps();
            // ... then do what you need to submit the form and load page 2
        });
    }

    if ($('#page2').length) {
        // this code must only run on page 2
        localStorage.setItem('steps', 'step2');
    }

});

Si basicamente:

Na página 1, quando o usuário envia o formulário, definimos um valor "etapas" em localStorage para indicar a etapa que o usuário realizou. Ao mesmo tempo, lançamos uma função com timeout que irá verificar se este valor foi alterado (por exemplo, 10 vezes / segundo).

Na página 2, alteramos imediatamente o referido valor.

Portanto, se o usuário usar o botão Voltar e o navegador restaurar a página 1 no estado exato em que a deixamos, a função checkSteps ainda está em execução e é capaz de detectar que o valor em localStorage foi alterado (e pode tomar a ação apropriada ) Uma vez que essa verificação tenha cumprido seu propósito, não há necessidade de continuar a executá-la, então simplesmente não usamos mais setTimeout.

s427
fonte