Posición de destino: elementos adhesivos que se encuentran actualmente en un estado 'atascado'


110

posición: pegajosa funciona ahora en algunos navegadores móviles, por lo que puede hacer que una barra de menú se desplace con la página, pero luego se quede en la parte superior de la ventana gráfica cada vez que el usuario se desplace más allá de ella.

Pero, ¿qué pasa si desea cambiar un poco el estilo de su barra de menú pegajosa cada vez que se `` pega ''? por ejemplo, es posible que desee que la barra tenga esquinas redondeadas cada vez que se desplaza con la página, pero tan pronto como se adhiera a la parte superior de la ventana gráfica, desea deshacerse de las esquinas redondeadas superiores y agregar una pequeña sombra paralela debajo eso.

¿Existe algún tipo de pseudoelector (p ::stuck. Ej. ) Para apuntar a elementos que se han adherido position: sticky y están actualmente pegados? ¿O los proveedores de navegadores tienen algo como esto en proceso? Si no es así, ¿dónde lo solicitaría?

NÓTESE BIEN. Las soluciones de javascript no son buenas para esto porque en los dispositivos móviles, por lo general, solo obtiene un scrollevento cuando el usuario suelta el dedo, por lo que JS no puede saber el momento exacto en que se pasó el umbral de desplazamiento.

Respuestas:


104

Actualmente no hay ningún selector que se proponga para los elementos que están actualmente "atascados". El módulo Postioned Layout donde position: stickyse define tampoco menciona ningún selector de este tipo.

Las solicitudes de funciones para CSS se pueden publicar en la lista de correo estilo www . Creo que una :stuckpseudoclase tiene más sentido que un ::stuckpseudo-elemento, ya que estás buscando apuntar a los elementos mismos mientras están en ese estado. De hecho, hace algún tiempo:stuck se habló de una pseudoclase ; Se encontró que la principal complicación es una que afecta a casi cualquier selector propuesto que intente coincidir en función de un estilo renderizado o calculado: dependencias circulares.

En el caso de una :stuckpseudoclase, el caso más simple de circularidad ocurriría con el siguiente CSS:

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

Y podría haber muchos más casos extremos que serían difíciles de abordar.

Si bien en general se acepta que sería bueno tener selectores que coincidan en función de ciertos estados de diseño , desafortunadamente existen limitaciones importantes que hacen que su implementación no sea trivial. No aguantaría la respiración por una solución CSS pura para este problema en el corto plazo.


14
Es una pena. Yo también estaba buscando una solución a este problema. ¿No sería bastante fácil simplemente introducir una regla que diga que las positionpropiedades en un :stuckselector deben ignorarse? (una regla para los proveedores de navegadores, quiero decir, similar a las reglas sobre cómo lefttiene prioridad sobre, rightetc.))
powerbuoy

5
No es solo la posición ... imagina una :stuckque cambia el topvalor de 0a 300px, luego desplázate hacia abajo 150px... ¿debería quedarse o no? O pensar acerca de un elemento con position: stickyy bottom: 0en el que :stucktal vez cambia font-sizey por lo tanto el tamaño de los elementos (por lo tanto, cambiar el momento en el que debe pegarse) ...
Romano

3
Consulte github.com/w3c/csswg-drafts/issues/1660 donde la propuesta es tener eventos JS para saber cuándo algo se atasca / despega. Eso no debería tener los problemas que presenta un pseudo-selector.
Ruben

27
Creo que se pueden hacer los mismos problemas circulares con muchas pseudoclases ya existentes (por ejemplo: hover cambiando el ancho y: no (: hover) cambiando de nuevo). Me encantaría: pseudoclase estancada y pensar que el desarrollador debería ser responsable de no tener problemas circulares en su código.
Marek Lisý

12
Bueno ... realmente no entiendo esto como un error, es como decir que el whileciclo está mal diseñado porque permite un bucle sin fin :) Sin embargo, gracias por aclarar esto;)
Marek Lisý

25

En algunos casos, un simple IntersectionObserverpuede hacer el truco, si la situación permite pegarse a un píxel o dos fuera de su contenedor raíz, en lugar de alinearlo correctamente. De esa manera, cuando se encuentra un poco más allá del borde, el observador dispara y nos ponemos en marcha.

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

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

Pegue el elemento fuera de su contenedor con top: -2px, y luego apunte a través del stuckatributo ...

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

Ejemplo aquí: https://codepen.io/anon/pen/vqyQEK


1
Creo que una stuckclase sería mejor que un atributo personalizado ... ¿Hay alguna razón específica para su elección?
collimarco

Una clase también funciona bien, pero esto parece un nivel un poco más alto que eso, ya que es una propiedad derivada. Un atributo me parece más apropiado, pero de cualquier manera es una cuestión de gustos.
rackable

Necesito que mi parte superior sea de 60 píxeles debido a un encabezado ya fijo, por lo que no puedo hacer que su ejemplo funcione
FooBar

1
Intente agregar algo de relleno superior a lo que sea que esté atascado, tal vez padding-top: 60pxen su caso :)
Tim Willis

5

Alguien en el blog de Google Developers afirma haber encontrado una solución de rendimiento basada en JavaScript con un IntersectionObserver .

Bit de código relevante aquí:

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

No lo he replicado yo mismo, pero tal vez ayude a alguien a tropezar con esta pregunta.


3

Realmente no soy un fanático de usar js hacks para diseñar cosas (es decir, getBoudingClientRect, escuchar scroll, escuchar cambiar de tamaño), pero así es como estoy resolviendo el problema actualmente. Esta solución tendrá problemas con las páginas que tienen contenido minimizable / maximizable (<detalles>), o desplazamiento anidado, o realmente cualquier bola curva. Dicho esto, es una solución simple para cuando el problema también es 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;
    });
})

Tenga en cuenta que el detector de eventos de desplazamiento se ejecuta en el hilo principal, lo que lo convierte en un asesino del rendimiento. En su lugar, utilice la API de Intersection Observer.
Jule escéptico

if (requestedFrame) { return; }No es un "asesino del rendimiento" debido al procesamiento por lotes de cuadros de animación. Sin embargo, Intersection Observer sigue siendo una mejora.
Seph Reed

0

Una forma compacta para cuando tienes un elemento encima del position:stickyelemento. Establece el atributo con el stuckque puede hacer coincidir en CSS 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'))
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.