Zielposition: klebrige Elemente, die sich derzeit in einem "festgefahrenen" Zustand befinden

106

position: sticky funktioniert jetzt in einigen mobilen Browsern, sodass Sie eine Menüleiste mit der Seite scrollen lassen und dann immer dann oben im Ansichtsfenster bleiben können, wenn der Benutzer daran vorbeirollt.

Aber was ist, wenn Sie Ihre klebrige Menüleiste leicht neu gestalten möchten, wenn sie gerade "klebt"? Sie möchten beispielsweise, dass die Leiste beim Scrollen mit der Seite abgerundete Ecken aufweist. Sobald sie jedoch am oberen Rand des Ansichtsfensters haftet, möchten Sie die oberen abgerundeten Ecken entfernen und einen kleinen Schlagschatten darunter hinzufügen es.

Gibt es irgendeine Art von Pseudoselektor (z. B. ::stuck) für Zielelemente, die haben position: sticky und derzeit hängen bleiben? Oder haben Browser-Anbieter so etwas in der Pipeline? Wenn nicht, wo würde ich es anfordern?

NB. Javascript-Lösungen sind dafür nicht geeignet, da auf Mobilgeräten normalerweise nur ein einziges scrollEreignis angezeigt wird, wenn der Benutzer seinen Finger loslässt, sodass JS nicht genau wissen kann, wann der Bildlaufschwellenwert überschritten wurde.

Callum
quelle

Antworten:

102

Derzeit wird kein Selektor für Elemente vorgeschlagen, die derzeit "stecken bleiben". Das Modul Postted Layout, in dem position: stickydefiniert ist, erwähnt auch keinen solchen Selektor.

Funktionsanfragen für CSS können in die Mailingliste im WWW-Stil gestellt werden . Ich glaube, eine :stuckPseudoklasse ist sinnvoller als ein ::stuckPseudoelement, da Sie versuchen, die Elemente selbst anzuvisieren, während sie sich in diesem Zustand befinden. Tatsächlich :stuckwurde vor einiger Zeit eine Pseudoklasse diskutiert ; Es wurde festgestellt, dass die Hauptkomplikation eine ist, die nahezu jeden vorgeschlagenen Selektor plagt, der versucht, basierend auf einem gerenderten oder berechneten Stil eine Übereinstimmung zu erzielen: zirkuläre Abhängigkeiten.

Im Fall einer :stuckPseudoklasse würde der einfachste Fall der Zirkularität mit dem folgenden CSS auftreten:

:stuck { position: static; /* Or anything other than sticky/fixed */ }
:not(:stuck) { position: sticky; /* Or fixed */ }

Und es könnte noch viel mehr Randfälle geben, die schwer zu lösen wären.

Obwohl allgemein anerkannt wird, dass es nett wäre, Selektoren zu haben, die auf bestimmten Layoutzuständen übereinstimmen , gibt es leider große Einschränkungen, die die Implementierung dieser nicht trivial machen. Ich würde nicht so schnell den Atem anhalten, um eine reine CSS-Lösung für dieses Problem zu finden.

BoltClock
quelle
13
Das ist eine Schande. Ich suchte auch nach einer Lösung für dieses Problem. Wäre es nicht ziemlich einfach, einfach eine Regel einzuführen, die besagt, dass positionEigenschaften eines :stuckSelektors ignoriert werden sollten? (Eine Regel für Browser-Anbieter, meine ich, ähnlich den Regeln, wie leftVorrang vor rightusw. hat))
Powerbuoy
5
Es ist nicht nur die Position ... stellen Sie sich eine vor :stuck, die den topWert von 0auf ändert 300pxund dann nach unten scrollt 150px... sollte sie bleiben oder nicht? Oder denken Sie an ein Element mit position: stickyund bottom: 0wo sich das :stuckmöglicherweise ändert font-sizeund daher die Größe des Elements (wodurch sich der Moment ändert, in dem es bleiben sollte) ...
Roman
3
Siehe github.com/w3c/csswg-drafts/issues/1660, wo vorgeschlagen wird, JS-Ereignisse zu haben, um zu wissen, wann etwas stecken bleibt / nicht mehr steckt. Das sollte nicht die Probleme haben, die ein Pseudo-Selektor einführt.
Ruben
27
Ich glaube, dass die gleichen zirkulären Probleme mit vielen bereits existierenden Pseudoklassen auftreten können (z. B.: Schwebeflug ändert die Breite und: nicht (: Schwebeflug) ändert sich wieder). Ich würde lieben: Pseudoklasse stecken und denken, dass der Entwickler dafür verantwortlich sein sollte, dass die zirkulären Probleme nicht in seinem Code enthalten sind.
Marek Lisý
12
Nun ... ich verstehe das nicht wirklich als Fehler - es ist, als würde man sagen, dass der whileZyklus schlecht gestaltet ist, weil er eine Endlosschleife zulässt :) Aber danke, dass
du das geklärt hast
24

In einigen Fällen kann ein Einfacher IntersectionObserverden Trick tun, wenn die Situation es erlaubt, an ein oder zwei Pixeln außerhalb seines Stammcontainers festzuhalten, anstatt richtig gegen ihn zu spülen. Auf diese Weise schießt der Beobachter, wenn es direkt hinter dem Rand sitzt, und wir rennen los.

const observer = new IntersectionObserver( 
  ([e]) => e.target.toggleAttribute('stuck', e.intersectionRatio < 1),
  {threshold: [1]}
);

observer.observe(document.querySelector('nav'));

Stecke das Element mit aus dem Container top: -2pxund ziele dann über das stuckAttribut ...

nav {
  background: magenta;
  height: 80px;
  position: sticky;
  top: -2px;
}
nav[stuck] {
  box-shadow: 0 0 16px black;
}

Beispiel hier: https://codepen.io/anon/pen/vqyQEK

zerbrechlich
quelle
1
Ich denke, dass eine stuckKlasse besser wäre als ein benutzerdefiniertes Attribut ... Gibt es einen bestimmten Grund für Ihre Wahl?
Collimarco
Eine Klasse funktioniert auch gut, aber dies scheint nur ein bisschen höher zu sein, da es sich um eine abgeleitete Eigenschaft handelt. Ein Attribut scheint mir angemessener zu sein, aber so oder so ist es Geschmackssache.
Rackable
Ich muss mein Top wegen eines bereits festen Headers auf 60px bringen, damit ich Ihr Beispiel nicht zum
Laufen bringen
1
Versuchen Sie, etwas Top-Polsterung zu dem hinzuzufügen, was gerade steckt, vielleicht padding-top: 60pxin Ihrem Fall :)
Tim Willis
5

Jemand im Google Developers-Blog behauptet, mit einem IntersectionObserver eine performative JavaScript-basierte Lösung gefunden zu haben .

Relevantes Codebit hier:

/**
 * 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));
}

Ich habe es nicht selbst repliziert, aber vielleicht hilft es jemandem, über diese Frage zu stolpern.

neo postmodern
quelle
3

Nicht wirklich ein Fan von js-Hacks zum Stylen von Dingen (z. B. getBoudingClientRect, Scroll-Listening, Größenänderung beim Listening), aber so löse ich derzeit das Problem. Diese Lösung hat Probleme mit Seiten mit minimierbarem / maximierbarem Inhalt (<Details>) oder verschachteltem Scrollen oder wirklich irgendwelchen Kurvenbällen. Davon abgesehen ist es eine einfache Lösung, wenn das Problem ebenfalls einfach ist.

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;
    });
})
Seph Reed
quelle
Beachten Sie, dass der Listener für Bildlaufereignisse im Hauptthread ausgeführt wird, was ihn zu einem Leistungskiller macht. Verwenden Sie stattdessen die Intersection Observer-API.
Skeptical Jule
if (requestedFrame) { return; }Aufgrund der Stapelverarbeitung von Animationsframes ist dies kein "Performance-Killer". Intersection Observer ist jedoch immer noch eine Verbesserung.
Seph Reed
0

Ein kompakter Weg, wenn Sie ein Element über dem position:stickyElement haben. Es legt das Attribut fest, mit stuckdem Sie in CSS übereinstimmen können header[stuck]:

HTML:

<img id="logo" ...>
<div>
  <header style="position: sticky">
    ...
  </header>
  ...
</div>

JS:

if (typeof IntersectionObserver !== 'function') {
  // sorry, IE https://caniuse.com/#feat=intersectionobserver
  return
}

new IntersectionObserver(
  function (entries, observer) {
    for (var _i = 0; _i < entries.length; _i++) {
      var stickyHeader = entries[_i].target.nextSibling
      stickyHeader.toggleAttribute('stuck', !entries[_i].isIntersecting)
    }
  },
  {}
).observe(document.getElementById('logo'))
Jonas Eberle
quelle