Posizione di destinazione: elementi appiccicosi che sono attualmente in uno stato "bloccato"


110

position: sticky funziona su alcuni browser mobili ora, quindi puoi far scorrere una barra dei menu con la pagina, ma poi rimanere nella parte superiore della visualizzazione ogni volta che l'utente la scorre oltre.

Ma cosa succede se si desidera ridisegnare leggermente la barra dei menu appiccicosa ogni volta che è attualmente "bloccata"? ad esempio, potresti volere che la barra abbia gli angoli arrotondati ogni volta che scorre con la pagina, ma non appena si attacca alla parte superiore della visualizzazione, vuoi eliminare gli angoli arrotondati superiori e aggiungere una piccola ombra sotto esso.

Esiste qualche tipo di pseudoselettore (ad esempio ::stuck) per scegliere come target elementi che hanno position: sticky e sono attualmente attaccati? O i fornitori di browser hanno qualcosa di simile in cantiere? In caso contrario, dove dovrei richiederlo?

NB. Le soluzioni javascript non vanno bene per questo perché sui dispositivi mobili di solito si ottiene solo un singolo scrollevento quando l'utente rilascia il dito, quindi JS non può sapere il momento esatto in cui è stata superata la soglia di scorrimento.

Risposte:


104

Al momento non è disponibile alcun selettore per gli elementi attualmente "bloccati". Il modulo Postioned Layout dove position: stickyè definito non menziona neanche alcun selettore di questo tipo.

Le richieste di funzionalità per CSS possono essere inviate alla mailing list in stile www . Credo che una :stuckpseudo-classe abbia più senso di uno ::stuckpseudo-elemento, dal momento che stai cercando di indirizzare gli elementi stessi mentre sono in quello stato. In effetti, tempo fa si:stuck è discusso di una pseudo-classe ; la complicazione principale, è stato scoperto, è quella che affligge quasi tutti i selettori proposti che tentano di trovare corrispondenze in base a uno stile renderizzato o calcolato: dipendenze circolari.

Nel caso di una :stuckpseudo-classe, il caso più semplice di circolarità si verificherebbe con il seguente CSS:

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

E potrebbero esserci molti altri casi limite che sarebbero difficili da affrontare.

Sebbene sia generalmente accettato che avere selettori che corrispondono in base a determinati stati di layout sarebbe bello , purtroppo esistono limitazioni importanti che li rendono non banali da implementare. Non tratterrei il respiro per una soluzione CSS pura a questo problema in qualunque momento presto.


14
È un peccato. Stavo cercando una soluzione anche a questo problema. Non sarebbe abbastanza facile introdurre semplicemente una regola che dice che le positionproprietà su un :stuckselettore dovrebbero essere ignorate? (una regola per i fornitori di browser, intendo, simile alle regole su come leftha la precedenza su rightecc.))
powerbuoy

5
Non è solo posizione ... immagina :stuckche cambi il topvalore da 0a 300px, quindi scorri verso il basso 150px... dovrebbe restare o no? Oppure pensate ad un elemento con position: stickye bottom: 0dove :stuckcambia il forse font-sizee quindi la dimensione degli elementi (cambiando quindi il momento in cui dovrebbe attaccarsi) ...
Romano

3
Vedi github.com/w3c/csswg-drafts/issues/1660 dove la proposta è di avere eventi JS per sapere quando qualcosa si blocca / sblocca. Questo non dovrebbe avere i problemi che introduce uno pseudo-selettore.
Ruben

27
Credo che gli stessi problemi circolari possano essere creati con molte pseudo-classi già esistenti (ad esempio: hover che cambia larghezza e: not (: hover) che cambia di nuovo). Mi piacerebbe: bloccato pseudo-classe e pensare che lo sviluppatore dovrebbe essere responsabile di non avere i problemi circolari nel suo codice.
Marek Lisý

12
Beh ... non lo capisco davvero come un errore - è come dire che il whileciclo è mal progettato perché consente un ciclo infinito :) Tuttavia, grazie per aver
chiarito

26

In alcuni casi un semplice IntersectionObserverpuò fare il trucco, se la situazione consente di rimanere attaccati a uno o due pixel all'esterno del suo contenitore principale, piuttosto che a livello appropriato. In questo modo, quando si trova appena oltre il bordo, l'osservatore spara e siamo fuori e corriamo.

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

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

Attacca l'elemento appena fuori dal suo contenitore con top: -2px, quindi scegli come target l' stuckattributo ...

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

Esempio qui: https://codepen.io/anon/pen/vqyQEK


1
Penso che una stuckclasse sarebbe meglio di un attributo personalizzato ... C'è qualche motivo specifico per la tua scelta?
collimarco

Anche una classe funziona bene, ma sembra un livello leggermente più alto di quello, poiché è una proprietà derivata. Un attributo mi sembra più appropriato, ma in ogni caso è una questione di gusti.
rackable

Ho bisogno che il mio massimo sia 60px a causa di un'intestazione già fissa, quindi non riesco a far funzionare il tuo esempio
FooBar

1
Prova ad aggiungere un po 'di imbottitura superiore a tutto ciò che è bloccato, forse padding-top: 60pxnel tuo caso :)
Tim Willis

5

Qualcuno nel blog di Google Developers afferma di aver trovato una soluzione performativa basata su JavaScript con un IntersectionObserver .

Bit di codice rilevante qui:

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

Non l'ho replicato da solo, ma forse aiuta qualcuno a inciampare su questa domanda.


3

Non sono proprio un fan dell'utilizzo di js hack per lo styling (ad esempio getBoudingClientRect, scorrimento dell'ascolto, ridimensionamento dell'ascolto), ma è così che sto attualmente risolvendo il problema. Questa soluzione avrà problemi con le pagine che hanno contenuto minimizzabile / massimizzabile (<details>), o scorrimento annidato, o qualsiasi tipo di curva. Detto questo, è una soluzione semplice anche quando il problema è semplice.

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

Si noti che il listener di eventi di scorrimento viene eseguito sul thread principale, il che lo rende un killer delle prestazioni. Utilizza invece l'Intersection Observer API.
Scettico Jule il

if (requestedFrame) { return; }Non è un "killer delle prestazioni" a causa del batch dei fotogrammi dell'animazione. Intersection Observer è ancora un miglioramento però.
Seph Reed

0

Un modo compatto per quando hai un elemento sopra l' position:stickyelemento. Imposta l'attributo stuckche puoi abbinare in CSS con 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'))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.