web-dev-qa-db-fra.com

Android Le bouton Précédent d'une application Web progressive ferme l'application)

Une application Web HTML5/Javascript (progressive) "pure" peut-elle intercepter le bouton de retour de l'appareil mobile afin d'éviter que l'application ne quitte?

Cette question est similaire à celle-ci mais je veux savoir s'il est possible d'obtenir un tel comportement sans dépendre de PhoneGap/Ionic ou Cordova.

19
rmpestano

Bien que le bouton Android retour ne puisse pas être directement connecté à partir d'un contexte d'application Web progressif, il existe une API d'historique que nous pouvons utiliser pour obtenir le résultat souhaité.

Tout d'abord, lorsqu'il n'y a pas d'historique de navigateur pour la page sur laquelle l'utilisateur se trouve, appuyer sur le bouton de retour ferme immédiatement l'application.
Nous pouvons empêcher cela en ajoutant un état d'historique précédent lors de la première ouverture de l'application:

window.addEventListener('load', function() {
  window.history.pushState({}, '')
})

La documentation de cette fonction se trouve sur mdn :

pushState () prend trois paramètres: un objet d'état, un titre (qui est actuellement ignoré) et (éventuellement) une URL [...] si elle n'est pas spécifiée, elle est définie sur l'URL actuelle du document.

Alors maintenant, l'utilisateur doit appuyer deux fois sur le bouton de retour. Une pression nous ramène à l'état historique d'origine, la prochaine presse ferme l'application.


La deuxième partie est que nous nous connectons à l'événement popstate de la fenêtre qui est déclenché chaque fois que le navigateur navigue en arrière ou en avant dans l'historique via une action de l'utilisateur (donc pas lorsque nous appelons history.pushState).

Un événement popstate est envoyé à la fenêtre chaque fois que l'entrée d'historique active change entre deux entrées d'historique pour le même document.

Nous avons donc maintenant:

window.addEventListener('load', function() {
  window.history.pushState({}, '')
})

window.addEventListener('popstate', function() {
  window.history.pushState({}, '')
})

Lorsque la page est chargée, nous créons immédiatement une nouvelle entrée d'historique, et chaque fois que l'utilisateur appuie sur "Retour" pour accéder à la première entrée, nous ajoutons à nouveau la nouvelle entrée!


Bien sûr, cette solution n'est très simple que pour les applications d'une seule page sans routage. Il devra être adapté pour les applications qui utilisent déjà l'api historique pour garder l'url actuelle en synchronisation avec l'endroit où l'utilisateur navigue.

Pour ce faire, nous allons ajouter un identifiant à l'objet d'état de l'historique. Cela nous permettra de profiter de l'aspect suivant de l'événement popstate:

Si l'entrée d'historique activée a été créée par un appel à history.pushState (), la propriété [...] d'état de l'événement popstate contient une copie de l'objet d'état de l'entrée d'historique.

Alors maintenant, pendant notre gestionnaire popstate, nous pouvons faire la distinction entre l'entrée d'historique que nous utilisons pour empêcher le comportement de back-button-closes-app par rapport aux entrées d'historique utilisées pour le routage dans l'application, et repousser uniquement notre historique préventif entrée quand il a été spécifiquement sauté:

window.addEventListener('load', function() {
  window.history.pushState({ noBackExitsApp: true }, '')
})

window.addEventListener('popstate', function(event) {
  if (event.state && event.state.noBackExitsApp) {
    window.history.pushState({ noBackExitsApp: true }, '')
  }
})

Le dernier comportement observé est que lorsque vous appuyez sur le bouton de retour, nous retournons dans l'historique du routeur de notre application Web progressive, ou nous restons sur la première page vue lors de l'ouverture de l'application.

26
alecdwm

@alecdwm, c'est du pur génie!

Non seulement cela fonctionne sur Android (dans Chrome et le navigateur Samsung), il fonctionne également dans les navigateurs Web de bureau. Je l'ai testé sur Chrome, Firefox et Edge sur Windows, et il est probable que les résultats soient les mêmes sur Mac. Je n'ai pas testé IE car eew. Même si vous concevez principalement pour des appareils iOS qui n'ont pas de bouton de retour, c'est toujours une bonne idée de s'assurer que les boutons de retour Android (et Windows Mobile ... awww ... Windows Mobile pauvre)) sont gérés de sorte que le PWA ressemble beaucoup plus à une application native.

Attacher un écouteur d'événement à l'événement de chargement n'a pas fonctionné pour moi, donc je viens de tricher et de l'ajouter à une fonction init window.onload existante que j'avais déjà de toute façon.

Gardez à l'esprit que cela pourrait frustrer les utilisateurs qui voudraient vraiment revenir à la page Web qu'ils consultaient avant de naviguer vers votre PWA tout en la parcourant en tant que page Web standard. Dans ce cas, vous pouvez ajouter un compteur et si l'utilisateur revient deux fois, vous pouvez réellement permettre à l'événement de retour "normal" de se produire (ou autoriser la fermeture de l'application).

Chrome sur Android a également (pour une raison quelconque) ajouté un état d'historique vide supplémentaire, il a donc fallu un retour supplémentaire pour revenir en arrière. Si quelqu'un a une idée à ce sujet, je serais curieux de connaître la raison.

Voici mon code anti-frustration:

var backPresses = 0;
var isAndroid = navigator.userAgent.toLowerCase().indexOf("Android") > -1;
var maxBackPresses = 2;
function handleBackButton(init) {
    if (init !== true)
        backPresses++;
    if ((!isAndroid && backPresses >= maxBackPresses) ||
    (isAndroid && backPresses >= maxBackPresses - 1)) {
        window.history.back();
    else
        window.history.pushState({}, '');
}
function setupWindowHistoryTricks() {
    handleBackButton(true);
    window.addEventListener('popstate', handleBackButton);
}
6
John T.

Cette approche présente quelques améliorations par rapport aux réponses existantes:

Permet à l'utilisateur de quitter s'il appuie deux fois en moins de 2 secondes: La meilleure durée est discutable mais l'idée d'autoriser une option de substitution est courante dans Android apps donc c'est souvent la bonne approche.

active uniquement ce comportement en mode autonome (PWA): cela garantit que le site Web se comporte comme prévu par l'utilisateur lorsqu'il se trouve dans un navigateur Web Android et applique uniquement cette solution de contournement) lorsque l'utilisateur voit le site Web présenté comme une "vraie application".

function isStandalone () {
    return !!navigator.standalone || window.matchMedia('(display-mode: standalone)').matches;
}

// Depends on bowser but wouldn't be hard to use a
// different approach to identifying that we're running on Android
function exitsOnBack () {
    return isStandalone() && browserInfo.os.name === 'Android';
}

// Everything below has to run at page start, probably onLoad

if (exitsOnBack()) handleBackEvents();

function handleBackEvents() {
    window.history.pushState({}, '');

    window.addEventListener('popstate', () => {
        //TODO: Optionally show a "Press back again to exit" tooltip
        setTimeout(() => {
            window.history.pushState({}, '');
            //TODO: Optionally hide tooltip
        }, 2000);
    });
}
0
Luckyrat

je ne voulais pas utiliser les fonctions natives de javascript pour gérer cela à l'intérieur d'une application de réaction, j'ai donc parcouru les solutions qui utilisaient react-router ou react-dom-router, mais au final, contre une date limite, js natif c'est ce qui l'a fait fonctionner. Ajout des écouteurs suivants à l'intérieur de componentDidMount() et définition de l'historique dans un état vide

window.addEventListener('load', function() {
  window.history.pushState({}, '')
})

window.addEventListener('popstate', function() {
  window.history.pushState({}, '')
})

cela a bien fonctionné sur le navigateur, mais ne fonctionnait toujours pas dans le PWA sur mobile enfin un collègue a découvert que le déclenchement des actions d'historique via le code était ce qui avait en quelque sorte initié les auditeurs et le tour est joué! tout est tombé en place

window.history.pushState(null, null, window.location.href); 
window.history.back(); 
window.history.forward(); 
0
Paulo