¿Cómo restablezco la escala / zoom de una aplicación web en un cambio de orientación en el iPhone?

96

Cuando inicio mi aplicación en modo vertical, funciona bien. Luego giro hacia el paisaje y se amplía. Para lograr que se escale correctamente para el modo horizontal, tengo que tocar dos veces en algo dos veces, primero para acercar completamente (el comportamiento normal de doble toque) y nuevamente para alejarlo completamente (nuevamente, el comportamiento normal de doble toque) . Cuando se aleja, se aleja a la escala NUEVA correcta para el modo horizontal.

Volver al retrato parece funcionar de manera más consistente; es decir, maneja el zoom para que la escala sea correcta cuando la orientación vuelve a cambiar a retrato.

Estoy tratando de averiguar si esto es un error. o si esto es algo que se puede arreglar con JavaScript?

Con el metacontenido de la ventana gráfica, estoy configurando la escala inicial en 1.0 y NO estoy configurando la escala mínima o máxima (ni quiero). Estoy configurando el ancho al ancho del dispositivo.

¿Algunas ideas? Sé que mucha gente estaría agradecida de tener una solución, ya que parece ser un problema persistente.

Elisabeth
fuente
1
Una solución perfecta: ¡sin javascript! stackoverflow.com/a/8727440/805787
Steeven

Respuestas:

89

Jeremy Keith ( @adactio ) tiene una buena solución para esto en su blog Orientación y escala

Mantenga el marcado escalable al no establecer una escala máxima en el marcado.

<meta name="viewport" content="width=device-width, initial-scale=1">

Luego, desactive la escalabilidad con javascript en carga hasta que comience el gesto cuando vuelva a permitir la escalabilidad con este script:

if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)) {
    var viewportmeta = document.querySelector('meta[name="viewport"]');
    if (viewportmeta) {
        viewportmeta.content = 'width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0';
        document.body.addEventListener('gesturestart', function () {
            viewportmeta.content = 'width=device-width, minimum-scale=0.25, maximum-scale=1.6';
        }, false);
    }
}

Actualización 22-12-2014:
En un iPad 1 esto no funciona, falla en el escucha de eventos. Descubrí que la eliminación .bodysoluciona eso:

document.addEventListener('gesturestart', function() { /* */ });
snobojohan
fuente
4
Seguramente esto es mejor que deshabilitar el zoom. La mejor solución que he encontrado hasta ahora :)
danwellman
Hmm, esto todavía deshabilita la capacidad de hacer zoom. ¿Alguien tiene una solución simple que no haga esto?
Brad Swerdfeger
Funciona, sin embargo, observé que el problema comienza de nuevo si uso el gesto de pellizcar y acercar y luego rotar la pantalla. No estoy seguro de cómo repararlo.
Nilesh
3
Funciona. Sin embargo, he notado que el usuario tiene que abrir dos pellizcos para hacer zoom. Supongo que esto se debe a que maximum-scale=1.0permanece vigente después de que comienza el gesto. ¿Hay alguna forma de arreglar esto?
LandonSchropp
3
Esto no funciona por 2 razones: 1) deshabilita el inicio de gestos número 1, lo que hace que el usuario necesite hacer dos gestos. 2) se rompe después de que el usuario dobla el primer gesto, por lo que realmente solo funciona si el usuario nunca hace ningún gesto. - todos deberían ver la solución de Andrew Ashbacher a continuación. Realmente funciona.
tmsimont
18

A Scott Jehl se le ocurrió una solución fantástica que utiliza el acelerómetro para anticipar los cambios de orientación. Esta solución responde muy bien y no interfiere con los gestos de zoom.

https://github.com/scottjehl/iOS-Orientationchange-Fix

Cómo funciona: esta corrección funciona escuchando el acelerómetro del dispositivo para predecir cuándo está a punto de ocurrir un cambio de orientación. Cuando considera que un cambio de orientación es inminente, el script deshabilita el zoom del usuario, lo que permite que el cambio de orientación ocurra correctamente, con el zoom deshabilitado. La secuencia de comandos restaura el zoom nuevamente una vez que el dispositivo está orientado casi hacia arriba o después de que su orientación ha cambiado. De esta manera, el zoom del usuario nunca se desactiva mientras la página está en uso.

Fuente minificada:

/*! A fix for the iOS orientationchange zoom bug. Script by @scottjehl, rebound by @wilto.MIT License.*/(function(m){if(!(/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1)){return}var l=m.document;if(!l.querySelector){return}var n=l.querySelector("meta[name=viewport]"),a=n&&n.getAttribute("content"),k=a+",maximum-scale=1",d=a+",maximum-scale=10",g=true,j,i,h,c;if(!n){return}function f(){n.setAttribute("content",d);g=true}function b(){n.setAttribute("content",k);g=false}function e(o){c=o.accelerationIncludingGravity;j=Math.abs(c.x);i=Math.abs(c.y);h=Math.abs(c.z);if(!m.orientation&&(j>7||((h>6&&i<8||h<8&&i>6)&&j>5))){if(g){b()}}else{if(!g){f()}}}m.addEventListener("orientationchange",f,false);m.addEventListener("devicemotion",e,false)})(this);
Andrew Ashbacher
fuente
¡Agradable! Parece una solución elegante.
Elisabeth
1
esta debe ser la respuesta aceptada !!!! Ojalá hubiera visto esto primero antes de perder una hora en las soluciones anteriores :)
tmsimont
1
después de más pruebas, esta es una solución poco confiable :( es inconsistente, y después de revisar el código puedo ver por qué ... el "umbral" de movimiento definido no siempre se alcanza, especialmente si sostiene el ipad en un ángulo mientras gira
tmsimont
Podría tener consecuencias desagradables para cualquier persona que utiliza bloqueo de rotación ... podrían sostener el teléfono en un ángulo determinado y perder la capacidad de zoom - el usuario no tendría ninguna idea de por qué
1owk3y
14

Tuve el mismo problema, y ​​establecer la escala máxima = 1.0 funcionó para mí.

Editar: como se menciona en los comentarios, esto deshabilita el zoom del usuario, excepto cuando el contenido excede la resolución de ancho. Como se mencionó, esto podría no ser prudente. También puede ser deseable en algunos casos.

El código de la ventana gráfica:

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0;">
rakaloof
fuente
Buena solucion. Hace un buen trabajo manteniendo la página en un nivel de zoom constante (en relación con el ancho del dispositivo) a través de cambios de orientación. ¡Gracias por compartirlo!
Luke Stevenson
17
la desventaja es que los usuarios discapacitados no pueden hacer zoom en su sitio.
Jess Jacobs
Me di cuenta de que todos estos métodos parecen evitar que CSS basado en consultas de medios registre correctamente el nuevo ancho del dispositivo (por ejemplo: @media all y (max-width: 479px)
mheavers
2
matar el zoom del usuario es una muy mala idea. vea la solución de Andrew Ashbacher a continuación
tmsimont
No estoy seguro acerca del iPhone, pero en el iPad esto no resuelve el problema, solo evita que el usuario pueda alejarse manualmente cuando el navegador acerca el cambio de orientación.
Alejo
3

Si tiene el ancho establecido en la ventana gráfica:

<meta name = "viewport" content = "width=device-width; initial-scale=1.0;
 maximum-scale=1.0;" />

Y luego cambie la orientación, se acercará aleatoriamente a veces (especialmente si está arrastrando en la pantalla) para solucionar esto, no establezca un ancho aquí que usé:

<meta id="viewport" name="viewport" content="initial-scale=1.0; user-scalable=0;
minimum-scale=1.0; maximum-scale=1.0" />

Esto corrige el zoom pase lo que pase, entonces puede usar el evento window.onorientationchange o si desea que sea independiente de la plataforma (útil para probar) el método window.innerWidth .

psyder
fuente
1

MobileSafari admite el orientationchangeevento en el windowobjeto. Desafortunadamente, no parece haber una forma de controlar directamente el zoom a través de JavaScript. Quizás podría escribir / cambiar dinámicamente la metaetiqueta que controla la ventana gráfica, pero dudo que eso funcione, solo afecta el estado inicial de la página. Quizás puedas usar este evento para cambiar el tamaño de tu contenido usando CSS. ¡Buena suerte!

Avi lino
fuente
3
¡Gracias! Sí, intenté cambiar dinámicamente los valores de la ventana gráfica de la metaetiqueta y no hizo nada. Me parece que si giras a Horizontal, quieres que se amplíe correctamente para mantener la escala de modo que la página se ajuste a la ventana de Safari. ¡Me parece muy extraño que este no sea el comportamiento predeterminado!
Elisabeth
1

He estado usando esta función en mi proyecto.

function changeViewPort(key, val) {
    var reg = new RegExp(key, "i"), oldval = document.querySelector('meta[name="viewport"]').content;
    var newval = reg.test(oldval) ? oldval.split(/,\s*/).map(function(v){ return reg.test(v) ? key+"="+val : v; }).join(", ") : oldval+= ", "+key+"="+val ;
    document.querySelector('meta[name="viewport"]').content = newval;
}

así que solo agregaEventListener:

if( /iPad|iPhone|iPod|Android/i.test(navigator.userAgent) ){
    window.addEventListener("orientationchange", function() { 
        changeViewPort("maximum-scale", 1);
        changeViewPort("maximum-scale", 10);
    }
}
James Yang
fuente
0

Encontré una nueva solución, diferente de cualquier otra que haya visto, al deshabilitar el zoom nativo de iOS y, en su lugar, implementar la funcionalidad de zoom en JavaScript.

Sérgio Lopes ofrece un excelente trasfondo sobre las otras soluciones al problema de zoom / orientación: una solución al famoso error de zoom de iOS en el cambio de orientación a retrato .

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" id="viewport" content="user-scalable=no,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0" />
    <title>Robocat mobile Safari zoom fix</title>
    <style>
        body {
            padding: 0;
            margin: 0;
        }
        #container {
            -webkit-transform-origin: 0px 0px;
            -webkit-transform: scale3d(1,1,1);
            /* shrink-to-fit needed so can measure width of container http://stackoverflow.com/questions/450903/make-css-div-width-equal-to-contents */
            display: inline-block;
            *display: inline;
            *zoom: 1;
        }
        #zoomfix {
            opacity: 0;
            position: absolute;
            z-index: -1;
            top: 0;
            left: 0;
        }
    </style>
</head>

<body>
    <input id="zoomfix" disabled="1" tabIndex="-1">
    <div id="container">
        <style>
            table {
                counter-reset: row cell;
                background-image: url(http://upload.wikimedia.org/wikipedia/commons/3/38/JPEG_example_JPG_RIP_010.jpg);
            }
            tr {
                counter-increment: row;
            }
            td:before {
                counter-increment: cell;
                color: white;
                font-weight: bold;
                content: "row" counter(row) ".cell" counter(cell);
            }
        </style>
        <table cellspacing="10">
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
            <tr><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td><td>
        </table>
    </div>

    <script>
    (function() {
        var viewportScale = 1;
        var container = document.getElementById('container');
        var scale, originX, originY, relativeOriginX, relativeOriginY, windowW, windowH, containerW, containerH, resizeTimer, activeElement;
        document.addEventListener('gesturestart', function(event) {
            scale = null;
            originX = event.pageX;
            originY = event.pageY;
            relativeOriginX = (originX - window.pageXOffset) / window.innerWidth;
            relativeOriginY = (originY - window.pageYOffset) / window.innerHeight;
            windowW = window.innerWidth;
            windowH = window.innerHeight;
            containerW = container.offsetWidth;
            containerH = container.offsetHeight;
        });
        document.addEventListener('gesturechange', function(event) {
            event.preventDefault();
            if (originX && originY && event.scale && event.pageX && event.pageY) {
                scale = event.scale;
                var newWindowW = windowW / scale;
                if (newWindowW > containerW) {
                    scale = windowW / containerW;
                }
                var newWindowH = windowH / scale;
                if (newWindowH > containerH) {
                    scale = windowH / containerH;
                }
                if (viewportScale * scale < 0.1) {
                    scale = 0.1/viewportScale;
                }
                if (viewportScale * scale > 10) {
                    scale = 10/viewportScale;
                }
                container.style.WebkitTransformOrigin = originX + 'px ' + originY + 'px';
                container.style.WebkitTransform = 'scale3d(' + scale + ',' + scale + ',1)';
            }
        });
        document.addEventListener('gestureend', function() {
            if (scale && (scale < 0.95 || scale > 1.05)) {
                viewportScale *= scale;
                scale = null;
                container.style.WebkitTransform = '';
                container.style.WebkitTransformOrigin = '';
                document.getElementById('viewport').setAttribute('content', 'user-scalable=no,initial-scale=' + viewportScale + ',minimum-scale=' + viewportScale + ',maximum-scale=' + viewportScale);
                document.body.style.WebkitTransform = 'scale3d(1,1,1)';
                // Without zoomfix focus, after changing orientation and zoom a few times, the iOS viewport scale functionality sometimes locks up (and completely stops working).
                // The reason I thought this hack would work is because showing the keyboard is the only way to affect the viewport sizing, which forces the viewport to resize (even though the keyboard doesn't actually get time to open!).
                // Also discovered another amazing side effect: if you have no meta viewport element, and focus()/blur() in gestureend, zoom is disabled!! Wow!
                var zoomfix = document.getElementById('zoomfix');
                zoomfix.disabled = false;
                zoomfix.focus();
                zoomfix.blur();
                setTimeout(function() {
                    zoomfix.disabled = true;
                    window.scrollTo(originX - relativeOriginX * window.innerWidth, originY - relativeOriginY * window.innerHeight);
                    // This forces a repaint. repaint *intermittently* fails to redraw correctly, and this fixes the problem.
                    document.body.style.WebkitTransform = '';
                }, 0);
            }
        });
    })();
    </script>
</body>
</html>

Podría mejorarse, pero para mis necesidades evita los principales inconvenientes que ocurren con todas las demás soluciones que he visto. Hasta ahora solo lo he probado usando Safari móvil en un iPad 2 con iOS4.

El enfoque () / desenfoque () es una solución para evitar el bloqueo ocasional de la función de zoom que puede ocurrir después de cambiar la orientación y hacer zoom unas cuantas veces.

La configuración de document.body.style fuerza un repintado de pantalla completa, lo que evita problemas intermitentes ocasionales en los que el repintado falla gravemente después del zoom.

robocat
fuente
0

Elisabeth, puede cambiar el contenido de la ventana gráfica de forma dinámica agregando la propiedad "id" a la metaetiqueta:

<meta name="viewport" id="view" content="user-scalable=yes, width=device-width minimum-scale=1, maximum-scale=1" />

Entonces puedes llamar por javascript:

document.getElementById("view").setAttribute('content','user-scalable=yes, width=device-width, minimum-scale=1, maximum-scale=10');
M Penades
fuente
@bridgestew si desea cambiar el zoom o la ventana gráfica, use dinámicamente la vista de desplazamiento de la subvista contenida en uiwebview. Agregué un fragmento de muestra en otro hilo: enlace
M Penades
4
@Elisabeth, ¿te funciona? No restablece el zoom al cambiar al modo horizontal para mí.
instanceof me
0

Aquí hay otra forma de hacerlo, que parece funcionar bien.

  1. Configure la metaetiqueta para restringir la ventana gráfica a escala = 1, lo que evita el zoom:

    <meta name = "viewport" content = "width = device-width, initial-scale = 1, minimum-scale = 1, maximum-scale = 1">

  2. Con javascript, cambie la metaetiqueta 1/2 segundo después para permitir el zoom:

    setTimeout (function () {document.querySelector ("meta [name = viewport]"). setAttribute ('content', 'width = device-width, initial-scale = 1');}, 500);

  3. Nuevamente con javascript, en el cambio de orientación, vuelva a cargar la página:

    window.onorientationchange = function () {window.location.reload ();};

Cada vez que reorienta el dispositivo, la página se recarga, inicialmente sin zoom. Pero 1/2 segundo después, se restablece la capacidad de hacer zoom.

Mark Lamprea
fuente
6
Responder a una pregunta 5 años después de haber sido formulada es algo ... Desafortunadamente, así no es como funciona la web en 2015. NO recargas la página cuando el usuario gira su dispositivo.
Pierre
0

Encontré una solución de fácil implementación. Establezca el foco en un elemento de texto que tenga un tamaño de fuente de 50 px al completar el formulario. No parece funcionar si el elemento de texto está oculto, pero ocultar este elemento se hace fácilmente configurando las propiedades de color de los elementos para que no tengan opacidad.

Dellsmash
fuente