Como faço para redefinir a escala / zoom de um aplicativo da web em uma mudança de orientação no iPhone?

96

Quando eu inicio meu aplicativo no modo retrato, ele funciona bem. Então eu giro para paisagem e é ampliado. Para obter a escala correta para o modo paisagem, tenho que tocar duas vezes em algo, primeiro para aumentar o zoom totalmente (o comportamento normal do toque duplo) e novamente para diminuir o zoom (novamente, o comportamento normal do toque duplo) . Quando diminui o zoom, ele diminui para a NOVA escala correta para o modo paisagem.

Voltar para o retrato parece funcionar de forma mais consistente; ou seja, ele controla o zoom para que a escala esteja correta quando a orientação voltar a ser retrato.

Estou tentando descobrir se isso é um bug? ou se isso é algo que pode ser corrigido com JavaScript?

Com o conteúdo meta da janela de visualização, estou definindo a escala inicial para 1,0 e NÃO estou definindo a escala mínima ou máxima (nem quero). Estou definindo a largura para a largura do dispositivo.

Alguma ideia? Eu sei que muitas pessoas ficariam gratas por uma solução, pois parece ser um problema persistente.

Elisabeth
fonte
1
Uma solução perfeita: sem javascript! stackoverflow.com/a/8727440/805787
Steeven

Respostas:

89

Jeremy Keith ( @adactio ) tem uma boa solução para isso em seu blog Orientação e escala

Mantenha a marcação escalonável, não definindo uma escala máxima na marcação.

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

Em seguida, desative a escalabilidade com javascript no carregamento até que o gestor seja reiniciado quando você permitir a escalabilidade novamente com 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);
    }
}

Atualização 22-12-2014:
Em um iPad 1 isso não funciona, falha no ouvinte de eventos. Descobri que a remoção .bodycorrige isso:

document.addEventListener('gesturestart', function() { /* */ });
esnobe
fonte
4
Certamente isso é melhor do que desativar o zoom ?! A melhor correção que encontrei até agora :)
danwellman
Hmm, isso ainda desativa a capacidade de zoom. Alguém tem uma solução simples que não faça isso?
Brad Swerdfeger de
Funciona, no entanto, observei que o problema começa novamente se eu usar o gesto pinch-zoom e girar a tela. Não sei como consertar.
Nilesh
3
Funciona. No entanto, percebi que o usuário precisa beliscar para abrir duas vezes para aumentar o zoom. Suponho que seja porque o maximum-scale=1.0efeito permanece após o início do gesto. Existe alguma maneira de corrigir isso?
LandonSchropp
3
Isso não funciona por 2 motivos: 1) desativa o gesturestart número 1, fazendo com que o usuário precise gesticular duas vezes. 2) ele quebra depois que o usuário duplica o primeiro gesto, então ele realmente só funciona se o usuário nunca fizer nenhum gesto. - todos devem olhar para a solução de Andrew Ashbacher abaixo. Realmente funciona.
tmsimont 01 de
18

Scott Jehl veio com uma solução fantástica que usa o acelerômetro para antecipar mudanças de orientação. Esta solução é muito responsiva e não interfere nos gestos de zoom.

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

Como funciona: essa correção funciona ouvindo o acelerômetro do dispositivo para prever quando uma mudança de orientação está prestes a ocorrer. Quando julgar iminente uma mudança de orientação, o script desabilita o zoom do usuário, permitindo que a mudança de orientação ocorra de maneira adequada, com o zoom desabilitado. O script restaura o zoom novamente quando o dispositivo é orientado próximo à vertical ou depois que sua orientação é alterada. Dessa forma, o zoom do usuário nunca é desabilitado enquanto a página está em uso.

Fonte reduzida:

/*! 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
fonte
Agradável! Parece uma solução elegante.
Elisabeth
1
esta deve ser a resposta aceita !!!! Eu gostaria de ter visto isso antes de perder uma hora com as soluções acima :)
tmsimont 01 de
1
depois de mais testes, esta é uma solução meio não confiável :( é inconsistente, e depois de examinar o código, posso ver por que ... o "limite" de movimento definido nem sempre é alcançado, especialmente se você estiver segurando o ipad em um ângulo durante a rotação
tmsimont
Pode ter consequências desagradáveis ​​para quem usa o bloqueio de rotação ... eles podem segurar o telefone em um determinado ângulo e perder a capacidade de zoom - o usuário não tem ideia do motivo
1owk3y
14

Eu tive o mesmo problema, e definir a escala máxima = 1,0 funcionou para mim.

Editar: Como mencionado nos comentários, isso desabilita o zoom do usuário, exceto quando o conteúdo excede a resolução de largura. Conforme mencionado, isso pode não ser sábio. Também pode ser desejado em alguns casos.

O código da janela de visualização:

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0;">
Rakaloof
fonte
Ótima solução. Faz um bom trabalho mantendo a página em um nível de zoom constante (em relação à largura do dispositivo) por meio de mudanças de orientação. Obrigado por compartilhar!
Luke Stevenson
17
a desvantagem é que usuários com deficiência não podem aumentar o zoom em seu site!
Jess Jacobs
Percebi que todos esses métodos parecem impedir que o CSS baseado em consulta de mídia registre a nova largura do dispositivo corretamente (ex: @media all e (max-width: 479px)
mheavers
2
matar o zoom do usuário é uma ideia muito ruim. veja a solução de Andrew Ashbacher abaixo
tmsimont 01 de
Não tenho certeza sobre o iPhone, mas no iPad isso não resolve o problema, apenas evita que o usuário seja capaz de diminuir o zoom manualmente quando o navegador aumentar o zoom na mudança de orientação.
Alejo
3

Se você tiver a largura definida na janela de visualização:

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

E então mude a orientação que irá aumentar o zoom aleatoriamente às vezes (especialmente se você estiver arrastando na tela) para corrigir isso não defina uma largura aqui que usei:

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

Isso corrige o zoom, aconteça o que acontecer, então você pode usar o evento window.onorientationchange ou, se quiser que seja independente da plataforma (útil para testar), o método window.innerWidth .

psyder
fonte
1

MobileSafari suporta o orientationchangeevento no windowobjeto. Infelizmente, não parece haver uma maneira de controlar diretamente o zoom via JavaScript. Talvez você possa escrever / alterar dinamicamente a metatag que controla a janela de visualização - mas eu duvido que funcione, isso afeta apenas o estado inicial da página. Talvez você possa usar este evento para realmente redimensionar seu conteúdo usando CSS. Boa sorte!

Avi Flax
fonte
3
Obrigado! Sim, tentei alterar dinamicamente os valores da janela de visualização da metatag e não adiantou. Parece-me que se você girar em Paisagem, você deseja aplicar o zoom corretamente para manter a escala de forma que a página se ajuste à janela do Safari. Parece muito estranho para mim que este não seja o comportamento padrão!
Elisabeth
1

Tenho usado essa função em meu projeto.

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

então apenas addEventListener:

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

Eu encontrei uma nova solução alternativa, diferente de qualquer outra que já vi, ao desativar o zoom nativo do iOS e, em vez disso, implementar a funcionalidade de zoom em JavaScript.

Um excelente pano de fundo sobre as várias outras soluções para o problema de zoom / orientação é de Sérgio Lopes: Uma correção para o famoso bug de zoom do iOS na mudança de orientação para 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>

Poderia ser melhorado, mas para minhas necessidades evita as principais desvantagens que ocorrem com todas as outras soluções que já vi. Até agora, só testei usando o Safari para celular em um iPad 2 com iOS4.

O foco () / desfoque () é uma solução alternativa para evitar o bloqueio ocasional da funcionalidade de zoom que pode ocorrer após alterar a orientação e aplicar zoom algumas vezes.

Definir o document.body.style força um repintura de tela inteira, o que evita problemas intermitentes ocasionais em que a repintura falha gravemente após o zoom.

robocat
fonte
0

Elisabeth, você pode alterar o conteúdo da janela de visualização dinamicamente adicionando a propriedade "id" à metatag:

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

Então você só pode ligar por javascript:

document.getElementById("view").setAttribute('content','user-scalable=yes, width=device-width, minimum-scale=1, maximum-scale=10');
M Penades
fonte
@bridgestew se você quiser alterar o zoom ou a janela de visualização, use dinamicamente a visualização de rolagem da subvisualização contida no uiwebview. Eu adicionei um fragmento de amostra em outro tópico: link
M Penades
4
@Elisabeth funciona para você? Ele não zera o zoom ao alternar para o modo paisagem para mim.
exemplo de mim de
0

Esta é outra maneira de fazer isso, que parece funcionar bem.

  1. Defina a metatag para restringir a janela de visualização a escala = 1, o que evita o zoom:

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

  2. Com javascript, altere a metatag 1/2 segundo depois para permitir o zoom:

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

  3. Novamente com javascript, na mudança de orientação, recarregue a página:

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

Cada vez que você reorienta o dispositivo, a página é recarregada, inicialmente sem zoom. Mas meio segundo depois, a capacidade de zoom é restaurada.

Mark Lamprey
fonte
6
Responder a uma pergunta 5 anos depois que ela foi feita é incrível. Infelizmente, não é assim que a web funciona em 2015. Você NÃO recarrega a página quando o usuário gira seu dispositivo.
Pierre
0

Encontrou uma correção de implementação muito fácil. Defina o foco para um elemento de texto que tenha um tamanho de fonte de 50 px ao completar o formulário. Parece não funcionar se o elemento de texto estiver oculto, mas ocultar esse elemento é facilmente feito configurando as propriedades de cor do elemento para não ter opacidade.

Dellsmash
fonte