Bemærk venligst, at dette blogindlæg blev udgivet i juni 2015, så afhængigt af hvornår du læser det, kan visse dele være forældede. Desværre kan jeg ikke altid holde disse indlæg fuldt opdaterede for at sikre, at oplysningerne forbliver nøjagtige.
- Min testside
- Forståelse af standardadfærd
- Forstå, hvad CSS-reglen "-webkit-overflow-scrolling" gør
- Brugeren skal vente, indtil gummibåndseffekten er færdig
- Sådan forhindrer du, at baggrundssiden ruller
- Sådan forhindrer du overscroll til baggrundssiden
- Sådan forhindrer du overscroll til baggrundssiden
- Hele sidens kildekode
For at løse et nyt scroll-problem i Safari på iOS var jeg nødt til at oprette min egen enkle testside og derefter anvende al den information og alle de kombinationer, jeg kunne finde på nettet, for at forsøge at løse problemet.
Nedenfor er en kort oversigt over, hvad jeg har fundet ud af i løbet af processen.
Min testside
Min testside var ret enkel. Den bestod af mock-data i et statisk div for at repræsentere 'siden', sammen med et fast div, der viste en overlejret 'menu' over siden, sådan her.
Den fulde kildekode til siden kan findes nederst i dette blogindlæg.
Forståelse af standardadfærd
Det første, jeg gjorde, var at prøve at forstå Safaris standardadfærd på iOS. Den mest bemærkelsesværdige observation var, at når du scrollede helt ned i menuen, udviste sidens indhold en 'gummibåndseffekt' - en hårdkodet funktion i Safari.
Det samme skete, da jeg scrollede helt op til toppen af menuen.
Men det første problem, jeg bemærkede, var, at hvis du fortsætter med at rulle helt op til toppen eller bunden, begynder Safari at vise renderingsfejl på både baggrundssiden og i menuen.
Jeg er ikke helt sikker på, hvorfor, men jeg formoder, at det har noget at gøre med gummibåndsfunktionen.
Forstå, hvad CSS-reglen "-webkit-overflow-scrolling" gør
Det næste, jeg gjorde, var at anvende CSS-reglen -webkit-overflow-scrolling på menuen, hvilket muliggør momentumbaseret rulning.
Som vist på billedet nedenfor fortsætter rulningen, selv efter at jeg har swipet med fingeren.
Men det tilføjer også en gummibåndseffekt, når du ruller helt til toppen eller bunden, som vist nedenfor.
Brugeren skal vente, indtil gummibåndseffekten er færdig
Under mine tests bemærkede jeg også, at mens gummibåndseffekten er i gang, kan brugeren ikke flytte fokus til et andet element, før animationen er helt afsluttet.
I eksemplet nedenfor ruller jeg først på baggrundssiden og prøver derefter hurtigt at rulle i menuen, men ender med at fortsætte med at rulle på baggrundssiden i stedet.
Men hvis jeg venter et øjeblik, indtil effekten er færdig, kan jeg derefter begynde at rulle gennem menuen.
Sådan forhindrer du, at baggrundssiden ruller
Under mine tests bemærkede jeg også, at mens gummibåndseffekten er i gang, kan brugeren ikke flytte fokus til et andet element, før animationen er helt afsluttet.
Baseret på dette svar fra Stack Overflow, kan du sikre, at elementer med disable-scrolling ikke udfører deres standard rulning, når touchmove-begivenheden udløses.
document.ontouchmove = function(event) {
var isTouchMoveAllowed = true,
target = event.target;
while (target !== null) {
if (target.classList && target.classList.contains('disable-scrolling')) {
isTouchMoveAllowed = false;
break;
}
target = target.parentNode;
}
if (!isTouchMoveAllowed) {
event.preventDefault();
}
};
Og anvend derefter klassen disable-scrolling på diven til siden:
<div class="page disable-scrolling">
Denne løsning fungerer sådan set, men mere omfattende tests viser, at baggrundssiden stadig kan rulle nogle gange, når du når helt øverst eller helt nederst i menuen.
Sådan forhindrer du overscroll til baggrundssiden
Ved at anvende denne løsning bliver det muligt at forhindre 'overscroll'. Det betyder, at når et element er scrollet helt op eller helt ned, fortsætter rulleningen ikke videre til selve siden.
Som et resultat forhindres kroppen i at scrolle, hvilket også forhindrer gummibandseffekten i at opstå.
function removeIOSRubberEffect(element) {
element.addEventListener("touchstart", function() {
var top = element.scrollTop,
totalScroll = element.scrollHeight,
currentScroll = top + element.offsetHeight;
if (top === 0) {
element.scrollTop = 1;
} else if (currentScroll === totalScroll) {
element.scrollTop = top - 1;
}
});
}
removeIOSRubberEffect(document.querySelector(".scrollable"));
Hvad denne JavaScript-kode gør, er at forhindre, at elementet nogensinde når helt op til toppen eller helt ned til bunden ved at holde det kun 1 pixel væk.
Hvis rullepositionen aldrig når den absolutte top eller bund, kan der ikke opstå overscroll.
Sådan forhindrer du overscroll til baggrundssiden
Hvis du vil fjerne rubber-band-effekten fra menuen – enten af æstetiske årsager eller fordi, som i mit tilfælde, den medførte yderligere rendereringsfejl – fjern blot CSS-reglen -webkit-overflow-scrolling.
Men som du kan se nedenfor mister du også den glatte rulningseffekten.
Hele sidens kildekode
Her er hele kildekoden til siden.
<!-- License MIT, Author Special Agent Squeaky (specialagentsqueaky.com) --> <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="minimum-scale=1.0, width=device-width, maximum-scale=1.0, user-scalable=no, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style> .page{ font-size: 24px; overflow: scroll; } .menu{ position: fixed; top: 0; bottom: 0; left: 0; width: 80%; background: gray; z-index: 1; font-size: 10px; overflow: scroll; /* uncomment to get smooth momentum scroll, but also a rubber band effect */ /*-webkit-overflow-scrolling: touch;*/ } .menu-item{ padding: 10px; background: darkgray; font-size: 24px; } </style>
</head>
<body>
<div class="menu scrollable">
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
<div class="menu-item">hello world</div>
</div>
<div class="page disable-scrolling">
Lorem Ipsum is simply dummy text of the printing and typesetting industry.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,
when an unknown printer took a galley of type and scrambled it to make a type
specimen book. It has survived not only five centuries, but also the leap into
electronic typesetting, remaining essentially unchanged. It was popularised in
the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,
and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</div>
<script>
document.ontouchmove = function(event) {
var isTouchMoveAllowed = true,
target = event.target;
while (target !== null) {
if (target.classList && target.classList.contains('disable-scrolling')) {
isTouchMoveAllowed = false;
break;
}
target = target.parentNode;
}
if (!isTouchMoveAllowed) {
event.preventDefault();
}
};
function removeIOSRubberEffect(element) {
element.addEventListener("touchstart", function() {
var top = element.scrollTop,
totalScroll = element.scrollHeight,
currentScroll = top + element.offsetHeight;
if (top === 0) {
element.scrollTop = 1;
} else if (currentScroll === totalScroll) {
element.scrollTop = top - 1;
}
});
}
removeIOSRubberEffect(document.querySelector(".scrollable"));
</script>
</body>
</html>