E se eu disser que há mais de uma viewport?
BRRRRAAAAAAMMMMMMMMMM
E a janela de visualização que você está usando agora é uma janela de visualização dentro de uma janela de visualização.
BRRRRAAAAAAMMMMMMMMMM
Às vezes, os dados fornecidos pelo DOM se referem a uma das viewports e não à outra.
BRRRRAAAAM… espera aí.
É 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 com zoom, além de um minimapa à direita mostrando a posição das viewports na página.
As coisas são bem simples durante a rolagem normal. A área verde
representa a viewport do layout, que os itens position: fixed
grudam.
As coisas ficam estranhas quando o zoom por pinça é introduzido. A caixa vermelha representa a
janela de visualização, que é a parte da página que podemos ver. Essa
janela de visualização pode se mover enquanto os elementos position: fixed
permanecem onde
estavam, anexados à janela de visualização do layout. Se você mover o cursor em um limite da janela de visualização
do layout, ela vai arrastar a janela de visualização do layout.
Como melhorar a compatibilidade
Infelizmente, as APIs da Web são inconsistentes em termos de qual viewport elas se referem e também são inconsistentes entre os navegadores.
Por exemplo, element.getBoundingClientRect().y
retorna o deslocamento dentro da
janela de visualização do layout. Isso é legal, mas muitas vezes queremos a posição na página.
Então 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 é interrompido quando o usuário faz zoom.
O Chrome 61 muda window.scrollY
para se referir à viewport do layout,
o que significa que o código acima funciona mesmo com zoom de pinça. Na verdade, os navegadores estão
mudando lentamente todas as propriedades posicionais para se referir à janela de visualização de layout.
Com exceção de uma nova propriedade…
Como expor a janela de visualização visual para o script
Uma nova API expõe a área de visualização visual como window.visualViewport
. É um rascunho de especificação, com aprovação em vários navegadores, e
está sendo lançado no Chrome 61.
console.log(window.visualViewport.width);
Confira o que window.visualViewport
nos dá:
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 de cima da janela de visualização visual e a janela de visualização do layout, em pixels CSS. |
pageLeft
|
Distância entre a borda esquerda da janela de visualização visual e o limite esquerdo do documento, em pixels CSS. |
pageTop
|
Distância entre a borda superior da janela de visualização visual e o limite superior do documento, em pixels CSS. |
width
|
Largura da viewport visual em pixels CSS. |
height
|
Altura da janela de visualização visual em pixels CSS. |
scale
|
A escala aplicada pelo zoom de aproximação. Se o conteúdo tiver o dobro do tamanho devido ao
zoom, ele retornará 2 . Isso não é afetado por
devicePixelRatio .
|
Há também alguns eventos:
window.visualViewport.addEventListener('resize', listener);
visualViewport eventos |
|
---|---|
resize
|
É acionado quando width , height ou
scale muda.
|
scroll
|
Acionado quando offsetLeft ou offsetTop muda.
|
Demonstração
O vídeo no início deste artigo foi criado usando visualViewport
.
Confira no Chrome 61 ou mais recente. Ele usa
visualViewport
para fixar o minimapa no canto superior direito da viewport
visual e aplica uma escala inversa para que ele sempre apareça com o mesmo tamanho,
mesmo com o gesto de pinça.
Problemas
Os eventos só são acionados quando a viewport visual muda
Parece uma coisa óbvia de declarar, mas me surpreendeu quando
joguei com visualViewport
pela primeira vez.
Se a viewport de layout for redimensionada, mas a viewport visual não for, você não receberá um
evento resize
. No entanto, é incomum que a janela de visualização de layout seja redimensionada sem
que a janela de visualização visual também mude a largura/altura.
O problema real é a rolagem. Se a rolagem ocorrer, mas a viewport visual
permanecer estática em relação à viewport de layout, você não receberá um evento scroll
em visualViewport
, e isso é muito comum. Durante a rolagem
normal do documento, a janela de visualização visual fica bloqueada no canto superior esquerdo da janela de
visualização do layout. Por isso, scroll
não é acionado em visualViewport
.
Se você quiser saber sobre todas as mudanças na viewport visual, incluindo
pageTop
e pageLeft
, também vai precisar detectar o evento de rolagem da janela:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
Evitar trabalho duplicado com vários listeners
Assim como detectar scroll
e resize
na janela, é provável que você chame
algum tipo de função "update" como resultado. No entanto, é comum que muitos desses eventos aconteçam ao mesmo tempo. Se o usuário redimensionar a janela, ela
acionará resize
, mas também scroll
com bastante frequência. Para melhorar o desempenho, 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
});
}
Enviei uma questão 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:
Buggy: usa um manipulador de eventos.
visualViewport.onscroll = () => console.log('scroll!');
Em vez disso, faça o seguinte:
Funciona: usa um listener de eventos
visualViewport.addEventListener('scroll', () => console.log('scroll'));
Os valores de deslocamento são arredondados
Acho que isso é outro bug do Chrome.
offsetLeft
e offsetTop
são arredondados, o que é bastante impreciso depois que o
usuário aumenta o zoom. É possível notar os problemas com isso durante a demonstração. Se o usuário aproximar e mover lentamente, o minimapa vai se encaixar entre os pixels sem zoom.
A taxa de eventos é lenta
Assim como outros eventos resize
e scroll
, eles não são acionados em todos os frames,
principalmente em dispositivos móveis. Você pode conferir isso durante a demonstração. Depois de aplicar o zoom, o minimapa
tem problemas para permanecer fixado na janela de visualização.
Acessibilidade
Na demonstração, usei visualViewport
para
evitar o zoom de pinça do usuário. Isso faz sentido para esta demonstração específica, mas
pense bem 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, você pode ocultar itens decorativos position: fixed
para
tirá-los do caminho do usuário. Mas, novamente, tome cuidado para não ocultar algo
que o usuário esteja tentando analisar mais de perto.
Você pode considerar postar em um serviço de análise quando o usuário ampliar. Isso pode ajudar a identificar páginas que os usuários estão tendo dificuldade no nível de zoom padrão.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
Pronto. visualViewport
é uma API que resolve problemas de
compatibilidade.