Veuillez noter que cet article a été publié en juin 2015. Par conséquent, selon la date à laquelle vous le lisez, certaines parties peuvent être obsolètes. Malheureusement, je ne peux pas toujours maintenir ces articles à jour pour garantir l'exactitude des informations.
- Ma page de test
- Comprendre le comportement par défaut
- Comprendre ce que fait la règle CSS « -webkit-overflow-scrolling »
- L'utilisateur doit attendre que l'effet de rebond se termine.
- Comment empêcher la page d'arrière-plan de défiler
- Comment éviter le défilement excessif vers la page d'arrière-plan
- Comment éviter le défilement excessif vers la page d'arrière-plan
- Code source de la page entière
Pour résoudre un problème de défilement récent dans Safari sur iOS, j'ai dû créer ma propre page de test simple, puis appliquer toutes les informations et combinaisons que j'ai pu trouver sur Internet afin d'essayer de résoudre le problème.
Ci-dessous, un bref aperçu de ce que j'ai découvert au cours de ce processus.
Ma page de test
Ma page de test était assez simple. Elle se composait de données factices dans une div statique pour représenter la « page », ainsi qu’une div fixe représentant un « menu » superposé au-dessus de la page, comme ceci.
Le code source complet de la page se trouve en bas de cet article de blog.
Comprendre le comportement par défaut
La première chose que j'ai faite a été d'essayer de comprendre le comportement par défaut de Safari sur iOS. L'observation la plus notable était que lorsque vous faisiez défiler jusqu'au bas du menu, le corps de la page affichait un « effet élastique » — une fonctionnalité codée en dur dans Safari.
La même chose s'est produite lorsque vous avez défilé jusqu'en haut du menu.
Cependant, le premier problème que j’ai remarqué est que, si vous faites défiler continuellement jusqu’en haut ou jusqu’en bas, Safari commence à afficher des artefacts de rendu sur la page d’arrière-plan et sur le menu.
Je ne suis pas sûr de la raison, mais je pense que cela a quelque chose à voir avec la fonctionnalité 'rubber-band'.
Comprendre ce que fait la règle CSS « -webkit-overflow-scrolling »
La prochaine chose que j'ai faite a été d'appliquer la règle CSS -webkit-overflow-scrolling au menu, qui permet le défilement avec élan.
Comme indiqué dans l'image ci-dessous, le défilement se poursuit même après avoir glissé le doigt.
Mais cela crée aussi un effet élastique lorsque vous faites défiler jusqu'en haut ou en bas, comme indiqué ci-dessous.
L'utilisateur doit attendre que l'effet de rebond se termine.
Lors de mes tests, j'ai également remarqué que, lorsque l'effet élastique est en cours, l'utilisateur ne peut pas déplacer le focus vers un autre élément tant que l'animation n'est pas entièrement terminée.
Dans l'exemple ci-dessous, je fais d'abord défiler la page d'arrière-plan, puis j'essaie rapidement de faire défiler le menu, mais je finis par continuer à faire défiler la page d'arrière-plan.
Cependant, si j'attends un instant que l'effet se termine, je peux ensuite commencer à faire défiler le menu.
Comment empêcher la page d'arrière-plan de défiler
Lors de mes tests, j'ai aussi remarqué que pendant que l'effet élastique est en cours, l'utilisateur ne peut pas déplacer le focus vers un autre élément tant que l'animation n'est pas entièrement terminée.
En vous basant sur cette réponse de Stack Overflow, vous pouvez vous assurer que les éléments ayant la classe disable-scrolling ne déclenchent pas leur action de défilement par défaut lorsque l'événement touchmove est déclenché.
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();
}
};
Et appliquez ensuite la classe disable-scrolling à la div de la page :
<div class="page disable-scrolling">
Ça fonctionne plutôt pas mal, mais des tests plus approfondis montrent que la page d'arrière-plan peut encore parfois défiler lorsque vous atteignez le sommet ou le bas du menu.
Comment éviter le défilement excessif vers la page d'arrière-plan
En appliquant ce correctif, il devient possible d'éviter le défilement excessif. Cela signifie que lorsque le défilement d'un élément atteint son sommet ou son bas, le défilement ne se propage pas dans le corps de la page.
En conséquence, le défilement du corps est bloqué, ce qui empêche également l'effet rebond de se produire.
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"));
Ce petit extrait JavaScript empêche l’élément d’atteindre le sommet ou le bas en le maintenant à seulement 1 pixel près.
Si la position de défilement n'atteint jamais le sommet absolu ni le bas absolu, le débordement de défilement ne peut jamais se produire.
Comment éviter le défilement excessif vers la page d'arrière-plan
Si vous souhaitez supprimer l'effet élastique du menu — que ce soit pour des raisons esthétiques ou parce que, comme dans mon cas, il a entraîné des problèmes de rendu supplémentaires — il suffit de supprimer la règle CSS -webkit-overflow-scrolling.
Cependant, comme vous pouvez le voir ci-dessous, vous perdez également l'inertie du défilement fluide.
Code source de la page entière
Voici le code source complet de la page.
<!-- 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>