E se eu dissesse que há mais de uma janela de visualização.
BRRRRAAAAAAAMMMMMMMMMM
A janela de visualização que você está usando agora é, na verdade, uma janela de visualização dentro de uma janela de visualização.
BRRRRAAAAAAAMMMMMMMMMM
Às vezes, os dados fornecidos pelo DOM se referem a uma dessas janelas de visualização, não à outra.
BRRRRAAAAM... espera o quê?
É verdade, confira:
Janela de visualização de layout x janela de visualização visual
O vídeo acima mostra uma página da Web sendo rolada e ampliada com o gesto de pinça, além de um minimapa à direita mostrando a posição das janelas de visualização na página.
As coisas são muito diretas durante a rolagem normal. A área verde
representa a janela de visualização de layout, à qual os itens position: fixed
se mantêm.
Tudo fica estranho quando o zoom com gesto de pinça é introduzido. A caixa vermelha representa a
janela de visualização visual, que é a parte da página que realmente podemos ver. Essa
janela de visualização pode se mover enquanto os elementos position: fixed
permanecem onde
estavam, anexados à janela de visualização de layout. Se o deslocamento for feito em um limite da janela
de visualização de layout, ela será arrastada junto.
Como melhorar a compatibilidade
Infelizmente, as APIs da Web são inconsistentes em relação à janela de visualização a que se referem e também são inconsistentes em todos os navegadores.
Por exemplo, element.getBoundingClientRect().y
retorna o deslocamento dentro da
janela de visualização de layout. Isso é legal, mas muitas vezes queremos a posição dentro da página,
por isso escrevemos:
element.getBoundingClientRect().y + window.scrollY
No entanto, muitos navegadores usam a janela de visualização visual para window.scrollY
, o que significa
que o código acima é corrompido quando o usuário faz gesto de pinça.
O Chrome 61 muda window.scrollY
para se referir à janela de visualização de layout,
ou seja, o código acima funciona mesmo quando o zoom é aplicado com o gesto de pinça. Na verdade, os navegadores estão
mudando lentamente todas as propriedades de posição para se referir à janela de visualização de layout.
Com exceção de uma nova propriedade...
Como expor a janela de visualização ao script
Uma nova API expõe a janela de visualização visual como window.visualViewport
. É uma especificação de rascunho, com aprovação para diferentes navegadores, e é
destinada ao Chrome 61.
console.log(window.visualViewport.width);
Confira o que o window.visualViewport
oferece:
visualViewport propriedades |
|
---|---|
offsetLeft
|
Distância entre a borda esquerda da janela de visualização visual e a janela de visualização de layout em pixels CSS. |
offsetTop
|
Distância entre a borda superior da janela de visualização visual e a janela de visualização de layout em pixels CSS. |
pageLeft
|
É a distância entre a borda esquerda da janela de visualização e o limite esquerdo do documento, em pixels CSS. |
pageTop
|
É a distância entre a borda superior da janela de visualização e o limite superior do documento, em pixels CSS. |
width
|
Largura da janela de visualização visual em pixels CSS. |
height
|
Altura da janela de visualização visual em pixels CSS. |
scale
|
A escala aplicada pelo zoom em pinça. Se o conteúdo tiver o dobro do tamanho devido ao zoom, isso vai retornar 2 . Isso não é afetado por
devicePixelRatio .
|
Há também alguns eventos:
window.visualViewport.addEventListener('resize', listener);
visualViewport eventos |
|
---|---|
resize
|
Disparado quando width , height ou
scale mudam.
|
scroll
|
Disparado quando offsetLeft ou offsetTop mudam.
|
Demonstração
O vídeo no início deste artigo foi criado usando visualViewport
.
Confira no Chrome 61 e versões mais recentes. Ele usa
visualViewport
para fazer com que o minimapa fique no canto superior direito da janela de visualização
visual e aplica uma escala inversa para que apareça sempre do mesmo tamanho,
apesar do zoom em pinça.
Pegadinhas
Os eventos só são disparados quando a janela de visualização visual é alterada
Parece uma coisa óbvia para dizer, mas isso me chamou pela primeira vez
quando jogou com visualViewport
.
Se a janela de visualização de layout for redimensionada, mas a de layout não, você não receberá um
evento resize
. No entanto, não é comum que a janela de visualização de layout seja redimensionada sem
que ela também mude a largura/altura.
O maior problema é rolar a tela. Se a rolagem ocorrer, mas a janela de visualização visual
permanecer estática em relação à janela de visualização de layout, você não vai receber um evento scroll
em visualViewport
, e isso é muito comum. Durante a rolagem normal do documento, a janela de visualização visual permanece bloqueada no canto superior esquerdo da janela
de layout. Portanto, scroll
não é acionado em visualViewport
.
Se quiser saber sobre todas as mudanças na janela de visualização visual, incluindo
pageTop
e pageLeft
, também é necessário detectar o evento de rolagem
da janela:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
Evite duplicar o trabalho com vários listeners
Assim como na detecção de scroll
e resize
na janela, você provavelmente vai chamar
algum tipo de função de atualização como resultado. No entanto, é comum que muitos
desses eventos aconteçam ao mesmo tempo. Se o usuário redimensionar a janela, resize
será
acionado, mas geralmente scroll
também. Para melhorar a performance, evite
processar a mudança várias vezes:
// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);
let pendingUpdate = false;
function update() {
// If we're already going to handle an update, return
if (pendingUpdate) return;
pendingUpdate = true;
// Use requestAnimationFrame so the update happens before next render
requestAnimationFrame(() => {
pendingUpdate = false;
// Handle update here
});
}
Registrei um problema de especificação para isso, porque acho que pode haver uma
maneira melhor, como um único evento update
.
Os manipuladores de eventos não funcionam
Devido a um bug do Chrome, isso não funciona:
Erros: usa um manipulador de eventos
visualViewport.onscroll = () => console.log('scroll!');
Como alternativa:
Funciona: usa um listener de eventos.
visualViewport.addEventListener('scroll', () => console.log('scroll'));
Os valores de deslocamento são arredondados
Acho que (bom, espero) esse é outro bug do Chrome.
offsetLeft
e offsetTop
são arredondados, o que é bastante impreciso quando o
usuário aumenta o zoom. É possível conferir os problemas com isso durante a demonstração. Se o usuário aumentar o zoom e movimentar
lentamente, o minimapa vai ser alinhado entre pixels sem zoom.
A taxa de eventos está lenta
Como outros eventos resize
e scroll
, eles não disparam todos os frames,
especialmente em dispositivos móveis. É possível observar isso durante a demonstração. Ao fazer o gesto de pinça, o minimapa terá problemas para permanecer fixo na janela de visualização.
Acessibilidade
Na demonstração, usei visualViewport
para
neutralizar o zoom de pinça do usuário. Faz sentido para essa demonstração específica, mas
pense com cuidado antes de fazer qualquer coisa que substitua o desejo do
usuário de aumentar o zoom.
O visualViewport
pode ser usado para melhorar a acessibilidade. Por exemplo, se o usuário
estiver aumentando o zoom, poderá ocultar itens position: fixed
decorativos para
tirá-los do caminho. Mas, novamente, tenha cuidado para não ocultar algo
que o usuário está tentando olhar mais de perto.
Você pode considerar postar em um serviço de análise quando o usuário aumenta o zoom. Isso ajuda a identificar as páginas com dificuldade para os usuários no nível de zoom padrão.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
Pronto. A visualViewport
é uma API pequena que resolve problemas de
compatibilidade ao longo do caminho.