Pozycja docelowa: lepkie elementy, które są obecnie w stanie „zablokowanym”

110

position: Sticky działa teraz w niektórych przeglądarkach mobilnych, więc możesz przewijać pasek menu wraz ze stroną, a następnie trzymać się górnej części widoku, gdy użytkownik przewinie obok niego.

Ale co, jeśli chcesz nieco zmienić styl swojego przyklejonego paska menu, gdy obecnie się „przykleja”? np. możesz chcieć, aby pasek miał zaokrąglone rogi za każdym razem, gdy przewija się wraz ze stroną, ale gdy tylko przylgnie do górnej części widoku, chcesz pozbyć się górnych zaokrąglonych rogów i dodać trochę cienia pod spodem to.

Czy istnieje jakiś rodzaj pseudoselektora (np. ::stuck) Do wskazywania elementów, które mają position: sticky i obecnie się trzymają? A może dostawcy przeglądarek mają coś takiego w przygotowaniu? Jeśli nie, gdzie mam o to poprosić?

NB. Rozwiązania javascript nie są do tego dobre, ponieważ na urządzeniach mobilnych zwykle otrzymujesz tylko jedno scrollzdarzenie, gdy użytkownik puści palec, więc JS nie może znać dokładnego momentu przekroczenia progu przewijania.

callum
źródło

Odpowiedzi:

104

Obecnie nie jest proponowany żaden selektor dla elementów, które są obecnie „zablokowane”. Moduł Postitional Layout, w którym position: stickyjest zdefiniowany, również nie wspomina o takim selektorze.

Prośby o dodanie funkcji CSS można wysyłać na listę mailingową w stylu www . Uważam, że :stuckpseudoklasa ma więcej sensu niż ::stuckpseudoelement, ponieważ chcesz kierować reklamy na same elementy, gdy są w tym stanie. W rzeczywistości :stuckpseudoklasa była omawiana jakiś czas temu ; Okazało się, że główną komplikacją jest taka, która nęka prawie każdy proponowany selektor, który próbuje dopasować w oparciu o renderowany lub obliczony styl: zależności cykliczne.

W przypadku :stuckpseudoklasy najprostszy przypadek cykliczności wystąpiłby z następującym CSS:

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

Może być o wiele więcej skrajnych przypadków, które byłyby trudne do rozwiązania.

Chociaż jest ogólnie przyjęte, że posiadanie selektorów dopasowanych na podstawie określonych stanów układu byłoby fajne , niestety istnieją poważne ograniczenia, które sprawiają, że ich implementacja jest nietrywialna. W najbliższym czasie nie wstrzymywałbym oddechu, szukając czystego CSS rozwiązania tego problemu.

BoltClock
źródło
14
Jaka szkoda. Szukałem też rozwiązania tego problemu. Czy nie byłoby całkiem łatwo wprowadzić regułę, która mówi, że positionwłaściwości :stuckselektora powinny być ignorowane? (chodzi mi o regułę dla dostawców przeglądarek, podobną do zasad dotyczących tego, jak leftma pierwszeństwo rightitp.))
powerbuoy
5
To nie tylko pozycja ... wyobraź sobie, :stuckże zmienia topwartość z 0na 300px, a następnie przewiń w dół 150px... czy ma się trzymać, czy nie? Albo pomyśl o elemencie z position: stickyi bottom: 0gdzie :stuckbyć może zmienia się, font-sizea tym samym rozmiar elementów (a więc zmieniając moment, w którym powinien się przykleić) ...
Roman
3
Zobacz github.com/w3c/csswg-drafts/issues/1660, gdzie propozycja ma mieć zdarzenia JS, aby wiedzieć, kiedy coś utknęło / odłączyło się. To nie powinno mieć problemów, które wprowadza pseudo-selektor.
Ruben
27
Uważam, że te same problemy cykliczne można zrobić z wieloma już istniejącymi pseudoklasami (np.: Po najechaniu kursorem zmiana szerokości i: nie (: najechanie) zmiana z powrotem). Bardzo bym chciał: utknąłem w pseudoklasie i pomyślałem, że programista powinien być odpowiedzialny za to, że nie ma cyrkularnych błędów w swoim kodzie.
Marek Lisý
12
No cóż ... nie rozumiem tego jako pomyłki - to tak, jakby powiedzieć, że whilecykl jest źle zaprojektowany, bo pozwala na nieskończoną pętlę :) Jednak dzięki za wyjaśnienie tego;)
Marek Lisý
26

W niektórych przypadkach prosty IntersectionObservermoże załatwić sprawę , jeśli sytuacja pozwala na przyklejanie się do jednego lub dwóch pikseli poza jego głównym pojemnikiem, a nie na prawidłowe wyrównanie. W ten sposób, gdy znajduje się tuż za krawędzią, obserwator strzela, a my ruszamy i uciekamy.

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

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

Umieść element tuż poza jego kontenerem za pomocą top: -2px, a następnie kieruj za pomocą stuckatrybutu ...

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

Przykład tutaj: https://codepen.io/anon/pen/vqyQEK

stelażowy
źródło
1
Myślę, że stuckklasa byłaby lepsza niż atrybut niestandardowy… Czy jest jakiś konkretny powód twojego wyboru?
collimarco
Klasa też działa dobrze, ale wydaje się, że poziom jest nieco wyższy, ponieważ jest to właściwość pochodna. Atrybut wydaje mi się bardziej odpowiedni, ale tak czy inaczej jest to kwestia gustu.
postawienia
Potrzebuję, aby moja góra miała 60 pikseli z powodu już ustalonego nagłówka, więc nie mogę uruchomić Twojego przykładu
FooBar
1
Spróbuj dodać trochę górnego wypełnienia do tego, co utknęło, może padding-top: 60pxw twoim przypadku :)
Tim Willis
5

Ktoś na blogu Google Developers twierdzi, że znalazł wydajne rozwiązanie oparte na JavaScript z IntersectionObserver .

Odpowiedni bit kodu tutaj:

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

Sam tego nie powielałem, ale może pomaga to komuś natknąć się na to pytanie.

neo post modern
źródło
3

Nie jestem fanem używania hacków js do stylizacji rzeczy (np. GetBoudingClientRect, słuchanie przewijania, słuchanie zmiany rozmiaru), ale w ten sposób obecnie rozwiązuję problem. To rozwiązanie będzie miało problemy ze stronami, które mają zawartość, którą można zminimalizować / zmaksymalizować (<szczegóły>) lub zagnieżdżone przewijanie, lub w ogóle jakiekolwiek krzywe kulki. To powiedziawszy, jest to proste rozwiązanie, gdy problem jest również prosty.

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
źródło
Zwróć uwagę, że detektor zdarzeń przewijania jest wykonywany w głównym wątku, co czyni go zabójcą wydajności. Zamiast tego użyj interfejsu API Intersection Observer.
Sceptyczny Jule
if (requestedFrame) { return; }To nie jest „zabójca wydajności” z powodu grupowania klatek animacji. Intersection Observer to jednak wciąż ulepszenie.
Seph Reed
0

Kompaktowy sposób, gdy masz element nad position:stickyelementem. Ustawia atrybut, stuckktóry możesz dopasować w CSS z 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
źródło