Observera att detta blogginlägg publicerades i juni 2015, så beroende på när du läser det kan vissa delar vara inaktuella. Tyvärr kan jag inte alltid hålla dessa inlägg helt uppdaterade för att säkerställa att informationen förblir korrekt.
- Min testsida
- Förstå standardbeteendet
- Förstå vad CSS-regeln "-webkit-overflow-scrolling" gör
- Användaren måste vänta tills gummibandseffekten avslutas.
- Så här förhindrar du att bakgrundssidan scrollar
- Hur du förhindrar att sidan scrollar till bakgrundssidan
- Så här förhindrar du överscroll till bakgrundssidan
- Hela sidans källkod
För att åtgärda ett nyligen uppkommet problem med rullningen i Safari på iOS var jag tvungen att skapa min egen enkla testsida och sedan använda all information och de kombinationer jag kunde hitta på nätet för att försöka lösa problemet.
Nedan följer en kort översikt över vad jag upptäckte under denna process.
Min testsida
Min testsida var ganska grundläggande. Den bestod av mockdata i en statisk div för att representera 'sidan', tillsammans med en fixerad div som representerar en överlagrad 'meny' ovanför sidan, så här.
Den fullständiga källkoden till sidan finns längst ner i detta blogginlägg.
Förstå standardbeteendet
Det första jag gjorde var att försöka förstå Safaris standardbeteende på iOS. Det mest slående var att när man rullade ända ner till botten av menyn visade sidans kropp en gummibandseffekt – en hårdkodad funktion i Safari.
Samma sak hände när man scrollade till längst upp i menyn.
Men det första problemet jag märkte var att om du kontinuerligt scrollar till toppen eller botten så uppstår renderingsfel på både bakgrundssidan och i menyn i Safari.
Jag vet inte riktigt varför, men jag misstänker att det har något att göra med gummibandsfunktionen.
Förstå vad CSS-regeln "-webkit-overflow-scrolling" gör
Nästa sak jag gjorde var att tillämpa CSS-regeln -webkit-overflow-scrolling på menyn, som möjliggör momentum-baserad scrollning.
Som bilden nedan visar fortsätter scrollningen även efter att jag sveper med mitt finger.
Men det ger också en gummibandseffekt när du scrollar till toppen eller botten, som visas nedan.
Användaren måste vänta tills gummibandseffekten avslutas.
Under min testning märkte jag också att medan gummibandseffekten pågår kan användaren inte flytta fokus till ett annat element förrän animationen har slutförts helt.
I nedanstående exempel rullar jag först bakgrundssidan och försöker sedan snabbt rulla i menyn, men jag fortsätter ändå att rulla bakgrundssidan.
Om jag väntar ett ögonblick tills effekten är färdig kan jag sedan börja scrolla i menyn.
Så här förhindrar du att bakgrundssidan scrollar
Under min testning märkte jag också att medan gummibandseffekten pågår kan användaren inte flytta fokus till ett annat element förrän animationen är helt färdig.
Baserat på detta svar från Stack Overflow kan du säkerställa att element med disable-scrolling inte utför sin standardrullningsåtgärd när touchmove-händelsen utlö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();
}
};
Och applicera sedan klassen disable-scrolling på sidans div-elementet:
<div class="page disable-scrolling">
Det här fungerar till viss del, men mer omfattande tester visar att bakgrundssidan fortfarande ibland kan scrolla när du når menyns översta eller nedersta del.
Hur du förhindrar att sidan scrollar till bakgrundssidan
Genom att tillämpa denna fix går det att förhindra överscrollning. Det innebär att när ett element scrollas till sin absoluta topp eller botten, fortsätter inte scrollningen in i sidans innehåll.
Som ett resultat förhindras sidan från att scrolla, vilket också förhindrar att gummibandseffekten uppstår.
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"));
Det här JavaScript-snippetet gör så att elementet aldrig når toppen eller botten helt och hållet, utan hålls endast 1 pixel från både toppen och botten.
Om scrollpositionen aldrig når den absoluta toppen eller botten, kan överscrollning aldrig uppstå.
Så här förhindrar du överscroll till bakgrundssidan
Om du vill ta bort gummibandseffekten i menyn – antingen av estetiska skäl eller eftersom, som i mitt fall, den orsakade ytterligare renderingfel – ta helt enkelt bort CSS-regeln -webkit-overflow-scrolling.
Men, som du kan se nedan, förlorar du också den smidiga rullningen.
Hela sidans källkod
Här är hela sidans källkod.
<!-- 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>