Wat als ik je vertelde dat er meer dan één viewport is?
BRRRRAAAAAAAMMMMMMMMMM
En de viewport die u nu gebruikt, is eigenlijk een viewport binnen een viewport.
BRRRRAAAAAAAMMMMMMMMMM
En soms verwijzen de gegevens die de DOM u geeft, naar een van die viewports en niet naar de andere.
BRRRRAAAAM... wacht wat?
Het is waar, kijk eens:
Lay-outvenster versus visuele venster
De video hierboven toont een webpagina waarop wordt gescrolld en geknuppeld, samen met een minikaart aan de rechterkant die de positie van viewports op de pagina laat zien.
Tijdens normaal scrollen zijn de zaken vrij eenvoudig. Het groene gebied vertegenwoordigt het lay-outvenster , op welke position: fixed
items blijven plakken.
Het wordt raar als knijpzoomen wordt geïntroduceerd. Het rode vak vertegenwoordigt de visuele viewport , het deel van de pagina dat we daadwerkelijk kunnen zien. Dit zichtvenster kan tijdens position: fixed
elementen blijven waar ze waren, vastgemaakt aan het lay-outvenster. Als we naar een grens van het lay-outvenster pannen, wordt het lay-outvenster meegesleept.
Compatibiliteit verbeteren
Helaas zijn web-API's inconsistent wat betreft de viewport waarnaar ze verwijzen, en ze zijn ook inconsistent in alle browsers.
element.getBoundingClientRect().y
retourneert bijvoorbeeld de offset binnen het lay-outvenster . Dat is cool, maar we willen vaak de positie binnen de pagina, dus schrijven we:
element.getBoundingClientRect().y + window.scrollY
Veel browsers gebruiken echter de visuele viewport voor window.scrollY
, wat betekent dat de bovenstaande code breekt wanneer de gebruiker knijpt in en uitzoomt.
Chrome 61 verandert window.scrollY
om in plaats daarvan naar de lay-outviewport te verwijzen, wat betekent dat de bovenstaande code zelfs werkt als er met een knijpbeweging wordt ingezoomd. In feite veranderen browsers langzaam alle positionele eigenschappen om naar de lay-outvenster te verwijzen.
Met uitzondering van één nieuw pand…
De visuele viewport blootstellen aan script
Een nieuwe API stelt de visuele viewport beschikbaar als window.visualViewport
. Het is een conceptspecificatie , met goedkeuring voor meerdere browsers , en komt terecht in Chrome 61.
console.log(window.visualViewport.width);
Dit is wat window.visualViewport
ons geeft:
visualViewport -eigenschappen | |
---|---|
offsetLeft | Afstand tussen de linkerrand van het visuele venster en het lay-outvenster, in CSS-pixels. |
offsetTop | Afstand tussen de bovenrand van het visuele venster en het lay-outvenster, in CSS-pixels. |
pageLeft | Afstand tussen de linkerrand van het visuele venster en de linkergrens van het document, in CSS-pixels. |
pageTop | Afstand tussen de bovenrand van het visuele venster en de bovenrand van het document, in CSS-pixels. |
width | Breedte van de visuele viewport in CSS-pixels. |
height | Hoogte van de visuele viewport in CSS-pixels. |
scale | De schaal die wordt toegepast door knijpzoomen. Als de inhoud door het zoomen twee keer zo groot is, zou dit 2 opleveren. Dit wordt niet beïnvloed door devicePixelRatio . |
Er zijn ook een paar evenementen:
window.visualViewport.addEventListener('resize', listener);
visualViewport gebeurtenissen | |
---|---|
resize | Wordt geactiveerd wanneer width , height of scale verandert. |
scroll | Wordt geactiveerd wanneer offsetLeft of offsetTop verandert. |
Demo
De video aan het begin van dit artikel is gemaakt met visualViewport
, bekijk hem eens in Chrome 61+ . Het maakt gebruik van visualViewport
om de minikaart rechtsboven in de visuele viewport te laten staan, en past een omgekeerde schaal toe, zodat deze altijd even groot lijkt, ondanks knijpzoomen.
Gotcha's
Gebeurtenissen worden alleen geactiveerd als de visuele viewport verandert
Het voelt voor de hand liggend om te zeggen, maar het viel me op toen ik voor het eerst met visualViewport
speelde.
Als het formaat van het lay-outvenster wordt gewijzigd, maar het visuele venster niet, krijgt u geen resize
gebeurtenis. Het is echter ongebruikelijk dat het lay-outvenster wordt vergroot of verkleind zonder dat het visuele venster ook de breedte/hoogte verandert.
De echte valkuil is scrollen. Als er wordt gescrolld, maar de visuele viewport statisch blijft ten opzichte van de lay-outviewport , krijgt u geen scroll
gebeurtenis op visualViewport
, en dit is heel gebruikelijk. Tijdens normaal scrollen door documenten blijft de visuele viewport linksboven in de lay-outviewport vergrendeld, dus scroll
wordt niet geactiveerd op visualViewport
.
Als je wilt horen over alle wijzigingen in de visuele viewport, inclusief pageTop
en pageLeft
, moet je ook naar de scroll-gebeurtenis van het venster luisteren:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
Vermijd dubbel werk met meerdere luisteraars
Net als bij het luisteren naar scroll
en resize
in het venster, zul je daardoor waarschijnlijk een soort "update" -functie oproepen. Het komt echter vaak voor dat veel van deze gebeurtenissen tegelijkertijd plaatsvinden. Als de gebruiker het formaat van het venster wijzigt, wordt resize
geactiveerd, maar vaak wordt er ook scroll
. Om de prestaties te verbeteren, moet u voorkomen dat u de wijziging meerdere keren doorvoert:
// 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
});
}
Ik heb hiervoor een specificatieprobleem ingediend , omdat ik denk dat er misschien een betere manier is, zoals een enkele update
gebeurtenis.
Gebeurtenishandlers werken niet
Vanwege een Chrome-bug werkt dit niet :
Buggy – gebruikt een gebeurtenishandler
visualViewport.onscroll = () => console.log('scroll!');
In plaats van:
Werkt – gebruikt een gebeurtenislistener
visualViewport.addEventListener('scroll', () => console.log('scroll'));
Offsetwaarden zijn afgerond
Ik denk (nou ja, ik hoop) dat dit weer een Chrome-bug is.
offsetLeft
en offsetTop
zijn afgerond, wat behoorlijk onnauwkeurig is zodra de gebruiker heeft ingezoomd. Je kunt de problemen hiermee zien tijdens de demo : als de gebruiker inzoomt en langzaam beweegt, springt de minikaart tussen niet-ingezoomde pixels .
Het aantal evenementen is laag
Net als andere resize
en scroll
, activeren deze niet elk frame, vooral niet op mobiel. Je kunt dit zien tijdens de demo : als je eenmaal inzoomt, heeft de minikaart moeite om vergrendeld te blijven in de viewport.
Toegankelijkheid
In de demo gebruikte ik visualViewport
om de pinch-zoom van de gebruiker tegen te gaan. Het is logisch voor deze specifieke demo, maar je moet goed nadenken voordat je iets doet dat de wens van de gebruiker om in te zoomen teniet doet.
visualViewport
kan worden gebruikt om de toegankelijkheid te verbeteren. Als de gebruiker bijvoorbeeld inzoomt, kunt u ervoor kiezen om de decoratieve position: fixed
items, te verbergen, zodat deze niet in de weg staan van de gebruiker. Maar nogmaals, wees voorzichtig dat u niet iets verbergt dat de gebruiker van dichterbij probeert te bekijken.
U kunt overwegen om berichten te plaatsen op een analyseservice wanneer de gebruiker inzoomt. Dit kan u helpen pagina's te identificeren waarmee gebruikers problemen ondervinden op het standaard zoomniveau.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
En dat is het! visualViewport
is een leuke kleine API die onderweg compatibiliteitsproblemen oplost.