Позиція націлювання: липкі елементи, які зараз перебувають у стані "застрягання"


110

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

Але що робити, якщо ви хочете трохи змінити свою липку панель меню, коли вона на даний момент "стирчить"? наприклад, ви можете хотіти, щоб на панелі були закруглені кути щоразу, коли вона прокручується сторінкою, але потім, як тільки вона прилипає до вершини вікна перегляду, ви хочете позбутися верхніх закруглених кутів, а під ними додайте трохи тіні. це.

Чи є якийсь псевдоселектор (наприклад ::stuck) для націлювання елементів, які є position: sticky і наразі приклеюються? Або у постачальників браузерів є щось подібне в процесі роботи? Якщо ні, то де я б це попросив?

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

Відповіді:


104

Наразі не існує селектора, який пропонується для елементів, які наразі "застрягли". Модуль розміщеної розмітки, де 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 найближчим часом.


14
Це ганьба. Я також шукав рішення цієї проблеми. Чи не було б досить просто ввести правило, яке говорить про те, що positionвластивості :stuckселектора слід ігнорувати? (Я маю на увазі правило для постачальників браузерів, схоже на правила про те, як leftмає перевагу rightі т. д.))
powerbuoy

5
Це не просто положення ... уявіть собі, :stuckщо змінює topзначення з 0на 300px, а потім прокрутіть вниз 150px... має притримуватися чи ні? Або подумайте про елемент з position: stickyі bottom: 0де, :stuckможливо, змінюється, font-sizeа отже, розмір елементів (тому змінюється момент, коли він повинен прилипати) ...
Роман

3
Дивіться сторінку github.com/w3c/csswg-drafts/isissue/1660, де пропонується мати події JS, щоб знати, коли щось застрягає / відклеюється. Це не повинно мати проблем, які вводить псевдоселектор.
Рубен

27
Я вважаю, що такі ж кругові проблеми можуть бути зроблені і з багатьма вже існуючими псевдокласами (наприклад: курсор зміни ширини і: не (: наведення) знову зміниться назад). Мені б хотілося: застряг псевдоклас і думаю, що розробник повинен нести відповідальність за відсутність кругових проблем у своєму коді.
Marek Lisý

12
Ну ... я не дуже розумію це як помилку - це як би сказати, що whileцикл погано розроблений, тому що він дозволяє нескінченний цикл :) Однак дякую, що
очистив

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, прокручування прослуховування, зміна розміру прослуховування), але саме зараз я вирішую проблему. У цьому рішенні виникнуть проблеми зі сторінками, які містять змішуваний / максимізуючий вміст (<деталі>), або вкладені прокрутки, або справді будь-які кулі кривих. За словами, це просте рішення, коли проблема проста.

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 перехрестя.
Скептичний Джуль

if (requestedFrame) { return; }Це не "вбивця продуктивності" через пакет анімаційних кадрів. Переглядач перехрестя все ще вдосконалюється.
Seph Reed

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'))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.