Позиция прицеливания: липкие элементы, которые в данный момент находятся в «застрявшем» состоянии.

111

position: sticky теперь работает в некоторых мобильных браузерах, поэтому вы можете прокрутить строку меню вместе со страницей, но затем придерживаться верхней части области просмотра, когда пользователь прокручивает ее.

Но что, если вы хотите немного изменить стиль липкой строки меню, когда она в данный момент «застревает»? например, вы можете захотеть, чтобы полоса имела закругленные углы всякий раз, когда она прокручивается вместе со страницей, но затем, как только она прилипнет к верхней части окна просмотра, вы захотите избавиться от верхних закругленных углов и добавить небольшую тень под ними. Это.

Есть ли какой-нибудь псевдоселектор (например ::stuck) для нацеливания элементов, которые есть position: sticky и в настоящее время прилипают? Или у производителей браузеров есть что-то подобное в разработке? Если нет, то где мне его запросить?

NB. Решения javascript для этого не годятся, потому что на мобильных устройствах вы обычно получаете только одно scrollсобытие, когда пользователь отпускает палец, поэтому JS не может знать точный момент, когда был пройден порог прокрутки.

каллум
источник

Ответы:

104

В настоящее время нет селектора, который предлагается для элементов, которые в настоящее время «застряли». Модуль Postioned Layout, в котором position: stickyопределено, также не упоминает ни одного такого селектора.

Запросы функций для CSS можно отправлять в список рассылки в стиле www . Я считаю, что :stuckпсевдокласс имеет больше смысла, чем ::stuckпсевдоэлемент, поскольку вы хотите настроить таргетинг на сами элементы, пока они находятся в этом состоянии. Фактически, некоторое время назад:stuck обсуждался псевдокласс ; Было обнаружено, что основная сложность - это проблема, которая беспокоит практически любой предложенный селектор, который пытается сопоставить на основе визуализированного или вычисленного стиля: циклические зависимости.

В случае :stuckпсевдокласса простейший случай цикличности будет происходить со следующим CSS:

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

И может быть еще много крайних случаев, которые будет трудно решить.

Хотя все согласны с тем, что было бы неплохо иметь селекторы, соответствующие определенным состояниям макета , к сожалению, существуют серьезные ограничения, которые делают их нетривиальными для реализации. Я бы не стал задерживать дыхание для чистого CSS-решения этой проблемы в ближайшее время.

BoltClock
источник
14
Это позор. Я тоже искал решение этой проблемы. Разве не было бы довольно просто ввести правило, согласно которому positionсвойства в :stuckселекторе следует игнорировать? (я имею в виду правило для поставщиков браузеров, аналогичное правилам о том, как leftимеет приоритет над и rightт. д.))
powerbuoy
5
Это не просто позиция ... представьте, :stuckчто A меняет topзначение с 0на 300px, а затем прокручивает вниз 150px... должно оно прилипать или нет? Или подумайте об элементе, в котором может быть изменено position: stickyи bottom: 0где :stuckможет измениться, font-sizeи, следовательно, о размере элемента (следовательно, об изменении момента, в который он должен
Роман
3
См. Github.com/w3c/csswg-drafts/issues/1660, где предлагается иметь события JS, чтобы знать, когда что-то застревает / отключается. При этом не должно возникнуть проблем, связанных с псевдоселектором.
Рубен
27
Я считаю, что те же круговые проблемы могут быть выполнены со многими уже существующими псевдоклассами (например: изменение ширины при наведении и повторное изменение: not (: hover)). Я бы хотел: застрял в псевдоклассе и подумал, что разработчик должен нести ответственность за отсутствие циклических проблем в своем коде.
Marek Lisý
12
Ну ... Я не считаю это ошибкой - это все равно что сказать, что whileцикл плохо спроектирован, потому что он допускает бесконечный цикл :) Однако спасибо, что прояснили это;)
Marek Lisý
26

В некоторых случаях простое IntersectionObserverрешение может помочь, если ситуация позволяет придерживаться пикселя или двух вне его корневого контейнера, а не должным образом смыть. Таким образом, когда он находится за краем, наблюдатель стреляет, и мы бежим.

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

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

Вставьте элемент прямо из контейнера с помощью top: -2px, а затем нацелитесь через stuckатрибут ...

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

Пример здесь: https://codepen.io/anon/pen/vqyQEK

складной
источник
1
Я думаю, что stuckкласс лучше, чем настраиваемый атрибут ... Есть ли конкретная причина для вашего выбора?
collimarco,
Класс тоже работает нормально, но это просто кажется немного более высоким уровнем, чем этот, поскольку это производное свойство. Атрибут мне кажется более подходящим, но в любом случае это дело вкуса.
монтажа в стойку
Мне нужно, чтобы мой верх был 60 пикселей из-за уже фиксированного заголовка, поэтому я не могу заставить ваш пример работать
FooBar
1
Попробуйте добавить верхний отступ к тому, что застряло, может быть, padding-top: 60pxв вашем случае :)
Тим Уиллис,
5

Кто-то в блоге разработчиков Google утверждает, что нашел перформативное решение на основе JavaScript с IntersectionObserver .

Соответствующий бит кода здесь:

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

Я сам не воспроизводил это, но, возможно, это поможет кому-то, кто споткнется в этом вопросе.

нео пост модерн
источник
3

Не совсем фанат использования js-хаков для стилизации вещей (например, getBoudingClientRect, прослушивания прокрутки, прослушивания изменения размера), но в настоящее время я решаю проблему именно так. Это решение будет иметь проблемы со страницами, которые имеют минимизируемое / максимизируемое содержимое (<details>), или вложенную прокрутку, или вообще какие-либо кривые шары. При этом это простое решение, когда проблема также проста.

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;
    });
})
Сеф Рид
источник
Обратите внимание, что прослушиватель событий прокрутки выполняется в основном потоке, что снижает производительность. Вместо этого используйте API-интерфейс Intersection Observer.
Скептически настроенный июль
if (requestedFrame) { return; }Это не "убийца производительности" из-за пакетной обработки кадров анимации. Однако Intersection Observer по-прежнему является улучшением.
Сеф Рид,
0

Компактный способ, когда у вас есть элемент над position:stickyэлементом. Он устанавливает атрибут, stuckкоторый вы можете сопоставить в 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'))
Йонас Эберле
источник