La instalación de las PWA varía según el navegador en el que nos encontramos.
Tienen un sistema automatizado, que identifica la pwa y ofrece la posibilidad de instalarla, colocando en la barra de direcciones un botón para hacerlo.

Al pulsar en el icono nos saca un mensaje con la posibilidad de instalarlo o no, si lo aceptamos se instalará la aplicación colocando un acceso en la pantalla de inicio.

En el caso de Chrome o Edge en un ordenador nos abre una ventana externa al navegador y cierra la pestaña que estaba usando. Para desinstalar una aplicación tenemos una opción pulsando en el botón menú (3 puntos verticales en Chrome u horizontales en Edge) que nos coloca en la franja superior de la aplicación.
En Windows crea una nueva carpeta en aplicaciones que ha llamado aplicaciones de Chrome en la quer coloca el icono de la aplicación, en el caso de Edge nos coloca un icono de la aplicación en el listado de aplicaciones sin carpeta.
En Mac nos crea un icono en el launcpad.
En Safari no admite el sistema automatizado de instalación pero nos permite añadir un icono a la pantalla de inicio en el sistema IOS para móviles y tablets, en el sistema mac para ordenadores no permite añadir a la pantalla de incio.
En otros navegadores o versiones antiguas de Chrome, no hay posibilidad de instalación automática.
Dado que varia el modo de instalar una PWA dependiendo del navegador que estemos utilizando y que en algunos no hay una activación automática del aviso, conviene programar la aparición y aspecto del mismo.
En la index creamos un contenedor que por defecto no esta visible que contendrá el aviso de que la aplicación se puede instalar, para aquellos que no sean conscientes del icono que se coloca en la barra del navegador.
Ese aviso puede montarse como se quiera, yo lo monto con una imagen, un texto y un botón en el caso de la instalación automática aunque la interacción es en todo el aviso.
En el caso de que la instalación no sea automática le pongo una acción cerrar el aviso a la imagen.
<div id="inst" class="inst" style="display:none;"> <img id="instimg" src="./img/pwainstbanner.jpg"> <div id="instcont"></div> </div>
En el javascript que inicia la aplicación y que recoge los datos ponemos las variables necesarias para el proceso de aviso de instalación:
var displayMode = 'browser tab';
if (navigator.standalone) { displayMode = 'standalone-ios'; }
if (window.matchMedia('(display-mode: standalone)').matches) { displayMode = 'standalone'; }
var instalada = false;
if(displayMode !== "browser tab") { instalada = true; }
//instalada = true;
var pwainstavis = {limit:3,state:true,auto:true,valor:0,act(){ this.valor++; pwainstcheck(); }};
if(instalada) { pwainstavis.state = false; }
var pwainstobj;
var deferredPrompt;
Conviene identificar el dispositivo para tomar decisiones en cuanto a como mostrar el aviso:
if(dispositivo.navegador === "safari") {
pwainstavis.auto = false;
if(dispositivo.sistemaCod ≡ "mac" && dispositivoCod !== "movil") { pwainstavis.state = false; }
}
Aquí discriminamos Safari, porque en los demás va a intentar la instalación automática (peainstavis.auto) y si no puede no mostrará nada.
Cuando marcamos pwainstavis.state como false, no mostrará aviso.
Para sacar el aviso el modo automático comprueba que hayas usado la aplicación al menos 30 segundos, y si es manual lo que hacemos es contar las interacciones (una interacción la decidimos en la programación, cambiar de pantalla , pulsar …).
El código para manejar la automática es:
window.addEventListener('appinstalled', () => { pwainstavis.state = false; });
if(pwainstavis.state) {
window.addEventListener('beforeinstallprompt', (e) => {
if(pwainstavis.auto) {
e.preventDefault();
deferredPrompt = e;
pwainstobj.addEventListener('click', async () => {
pwainstchange('off');
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
pwainstresp(outcome);
deferredPrompt = null;
});
if(pwainstavis.state) { pwainstchange('on'); }
pwainstavis.state = false;
}
});
}
Para la instalación manual:
function pwainstcheck() {
if(!pwainstavis.auto) {
if(pwainstavis.valor === pwainstavis.limit) { if(pwainstavis.state) { pwainstchange('on'); } }
}
}
En la función en la que se inicia el interfaz de la aplicación personalizamos el aviso, dependiendo del dispositivo y cargando los textos desde una variable por si trabajamos en idiomas:
// para la instalación
let instappinfohtml = "";
if(pwainstavis.auto) {
instappinfohtml += '<p id="insttx">'+frases.instbanntx+'</p>'+"\n";
instappinfohtml += '<div id="instbot" class="instbot">'+frases.instbannbot+'</div>'+"\n";
}else{
let posicon = "";
if(dispositivo['sistemaCod'] === "ios" || dispositivo['sistemaCod'] === "mac") {
posicon = "abajo";
if(dispositivo['sistemaCod'] === "ios") {
instappinfohtml += '<p><b>'+frases.appinstmov+'</b></p>'+"\n";
}else{
posicon = "arriba";
instappinfohtml += '<p><b>'+frases.appinsttab+'</p></b>'+"\n";
}
switch(idi) {
default: instappinfohtml += '<p>Pulsa sobre el icono <span class="iconq-compartir"></span> que tienes '+posicon+', y muevete por el menú que saldrá para pulsar sobre la opción "Añadir a la pantalla de inicio <span class="iconq-addinicio"></span>".</p>'+"\n"; break;
}
}else{
if(dispositivo.dispositivoCod === "movil") {
instappinfohtml += '<p><b>'+frases.appinstmov+'</p></b>'+"\n";
}else{
instappinfohtml += '<p><b>'+frases.appinsttab+'</p></b>'+"\n";
}
switch(idi) {
default: instappinfohtml += '<p>Para instalar la aplicación pulsa sobre el icono <span class="iconq-menuchrome"></span> que tienes arriba, y muevete por el menú que saldrá para pulsar sobre la opción "Añadir a la pantalla de inicio".</p>'+"\n"; break;
}
}
instappinfohtml += '<p style="padding-bottom:10px;">'+frases.appinstadd+'</p>'+"\n";
}
document.getElementById("instcont").innerHTML = instappinfohtml;
let obj = document.getElementById("inst");
obj.style.display = "table";
let objsize = {};
objsize = {w:obj.offsetWidth,h:obj.offsetHeight};
document.getElementById("instimg").style.display = "table";
if(pwainstavis.auto) {
//document.getElementById("instcerrar").style.display = "none";
document.getElementById("instimg").setAttribute( "onClick", "" );
}else{
//document.getElementById("instcerrar").style.display = "table";
document.getElementById("instimg").setAttribute( "onClick", "pwainstchange('off')" );
}
obj.style.top = window.innerHeight + "px";
obj.style.left = (hueW - objsize.w)/2 + "px";
// precargo imagenes
let imagen = new Image();
imagen.onload = viewIniciado;
imagen.src = "./img/pwainstbanner.jpg";
Tenemos una función para manejar el aviso, mostrarlo y esconderlo:
function pwainstchange(acc) {
if(acc === "off") {
pwainstobj.style.opacity = "0";
pwainstobj.style.top = hueH + "px";
}else{
pwainstobj.style.opacity = "1";
pwainstobj.style.top = hueH - pwainstobj.offsetHeight + "px";
}
}
En algún sitio del código tenemos que llamar a la función pwainstcheck, para contabilizar las interacciones, en el ejemplo yo lo hago al pulsar sobre el punto gris:
<div onclick="pwainstavis.act();document.getElementById(\'numpul\').innerHTML = pwainstavis.valor;" style="width:50px;height:50px;margin:auto;margin-bottom:10px;border-radius:100%;background:#666;"></div>
En el navegador Firefox se han eliminado las posibilidades de añadir la aplicación al dispositivo, por lo que para ofrecer una experiencia mejor de uso conviene presentarla a pantalla completa eliminando las partes informativas del navegador. Este proceso no esta permitido automatizarlo, por lo que requiere de una interacción del usuario sobre un botón.
Para implementarlo añadimos un botón a la interfaz y lo dejamos no visible, para activarlo solo si se esta usando Firefox.
<div id="botfull" class="botfull boticon iconq-fullscreenon" onclick="fullscreen()" style="display:none;"></div>
Yo uso una librería propia para detectar el dispositivo del usuario que devuelve un objeto dispositivo. Una vez detectamos que el navegador es Firefox hacemos visible el botón:
if(dispositivo.navegador === "firefox") { document.getElementById("botfull").style.display = "block
Ese botón llama a la función fullscreen, que trabaja con la variable screenstate.
var screenstate = 'min';
function fullscreen() {
let elem = document.getElementsByTagName('body')[0];
if(screenstate === 'min') {
screenstate = "full";
document.getElementById('botfull').classList.remove('iconq-fullscreenon');
document.getElementById('botfull').classList.add('iconq-fullscreenoff');
}else{
screenstate = "min";
document.getElementById('botfull').classList.remove('iconq-fullscreenoff');
document.getElementById('botfull').classList.add('iconq-fullscreenon');
}
if(dispositivo.navegador === "firefox") {
if(screenstate === "full") { elem.requestFullscreen(); }else{ document.exitFullscreen(); }
}else{
if(document.webkitFullscreenElement) {
document.webkitCancelFullScreen();
}else{
elem.webkitRequestFullScreen();
};
}
}