position: sticky fonctionne maintenant sur certains navigateurs mobiles. Vous pouvez ainsi faire défiler une barre de menus avec la page, puis vous en tenir au haut de la fenêtre lorsque l'utilisateur le fait défiler.
Mais que se passe-t-il si vous souhaitez redessiner légèrement la barre de menu collante chaque fois que celle-ci est "collante"? vous voudrez peut-être par exemple que la barre ait des coins arrondis à chaque défilement de la page, mais dès que la barre reste collée en haut de la fenêtre, vous souhaitez supprimer les coins arrondis supérieurs et ajouter une petite ombre portée en dessous. il.
Existe-t-il un type de pseudo-sélecteur (par exemple, ::stuck
) pour cibler les éléments sur lesquels position: sticky
et sont actuellement bloqués? Ou les éditeurs de navigateurs ont-ils quelque chose de semblable dans le pipeline? Si non, où pourrais-je le demander?
NB Les solutions javascript ne conviennent pas à cela, car sur mobile, un utilisateur ne reçoit généralement qu'un seul événement scroll
lorsque l'utilisateur relâche son doigt. JS ne peut donc pas connaître le moment exact où le seuil de défilement a été passé.
Il n'y a actuellement aucun sélecteur proposé pour les éléments actuellement «bloqués». Le module Postional Layout où position: sticky
est défini ne mentionne pas non plus un tel sélecteur.
Les demandes de fonctionnalités pour CSS peuvent être postées sur la liste de diffusion www-style . Je pense qu'une pseudo-classe :stuck
a plus de sens qu'un pseudo-élément ::stuck
, car vous cherchez à cibler les éléments eux-mêmes lorsqu'ils se trouvent dans cet état. En fait, une pseudo-classe :stuck
a été discutée il y a quelque temps ; La principale complication, a-t-on trouvé, est celle qui gêne quasiment tout sélecteur proposé qui tente de faire correspondre une image en fonction d’un style rendu ou calculé: les dépendances circulaires.
Dans le cas d'une pseudo-classe :stuck
, le cas de circularité le plus simple se produirait avec le CSS suivant:
:stuck { position: static; /* Or anything other than sticky/fixed */ }
:not(:stuck) { position: sticky; /* Or fixed */ }
Et il pourrait y avoir beaucoup plus de cas Edge difficiles à traiter.
Bien qu’il soit généralement admis que la sélection de sélecteurs correspondant à certains états de la structure serait Nice, il existe malheureusement des limitations majeures qui rendent leur mise en œuvre non triviale. Je ne voudrais pas retenir mon souffle pour une solution CSS pure à ce problème de si tôt.
Dans certains cas, un simple IntersectionObserver
peut faire l'affaire, si la situation permet de coller à un pixel ou deux en dehors de son conteneur racine, plutôt que de le vider correctement. De cette façon, quand il est assis juste au-delà de Edge, l'observateur tire et nous partons en courant.
const observer = new IntersectionObserver(
([e]) => e.target.toggleAttribute('stuck', e.intersectionRatio < 1),
{threshold: [1]}
);
observer.observe(document.querySelector('nav'));
Collez l'élément qui vient de sortir de son conteneur avec top: -2px
, puis ciblez-le via l'attribut stuck
...
nav {
background: Magenta;
height: 80px;
position: sticky;
top: -2px;
}
nav[stuck] {
box-shadow: 0 0 16px black;
}
Exemple ici: https://codepen.io/anon/pen/vqyQEK
Pas vraiment fan de js hacks pour le styling (ie getBoudingClientRect, l'écoute de défilement, la redimensionnement d'écoute), mais c'est comme ça que je résous le problème. Cette solution posera des problèmes avec les pages au contenu minimizable/maximizable (<détails>), au défilement imbriqué, ou à toutes les billes courbes. Cela étant dit, c'est une solution simple lorsque le problème est simple.
let lowestKnownOffset: number = -1;
window.addEventListener("resize", () => lowestKnownOffset = -1);
const $Title = document.getElementById("Title");
let requestedFrame: number;
window.addEventListener("scroll", (event) => {
if (requestedFrame) { return; }
requestedFrame = requestAnimationFrame(() => {
// if it's sticky to top, the offset will bottom out at its natural page offset
if (lowestKnownOffset === -1) { lowestKnownOffset = $Title.offsetTop; }
lowestKnownOffset = Math.min(lowestKnownOffset, $Title.offsetTop);
// this condition assumes that $Title is the only sticky element and it sticks at top: 0px
// if there are multiple elements, this can be updated to choose whichever one it furthest down on the page as the sticky one
if (window.scrollY >= lowestKnownOffset) {
$Title.classList.add("--stuck");
} else {
$Title.classList.remove("--stuck");
}
requestedFrame = undefined;
});
})
Un utilisateur du blog Google Developers / affirme avoir trouvé une solution performative basée sur JavaScript avec un IntersectionObserver .
Bit de code pertinent ici:
/**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--top` become visible/invisible at the top of the container.
* @param {!Element} container
*/
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
// Started sticking.
if (targetInfo.bottom < rootBoundsInfo.top) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [0], root: container});
// Add the top sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--top');
sentinels.forEach(el => observer.observe(el));
}
Je ne l'ai pas reproduit moi-même, mais peut-être que cela aiderait quelqu'un à trébucher sur cette question.