Posisi penargetan: elemen melekat yang saat ini berada dalam status 'macet'

110

position: sticky berfungsi di beberapa browser seluler sekarang, jadi Anda dapat membuat bilah menu menggulir dengan laman tetapi kemudian tetap berada di bagian atas area pandang setiap kali pengguna menggulir melewatinya.

Tetapi bagaimana jika Anda ingin sedikit mengubah gaya bilah menu lengket Anda setiap kali saat ini 'menempel'? misalnya, Anda mungkin ingin bilah memiliki sudut membulat setiap kali bergulir dengan halaman, tetapi segera setelah bilah menempel di bagian atas viewport, Anda ingin menyingkirkan sudut membulat atas, dan menambahkan sedikit bayangan di bawahnya Itu.

Apakah ada jenis pseudoselector (misalnya ::stuck) untuk menargetkan elemen yang telah position: sticky dan saat ini melekat? Atau apakah vendor browser memiliki hal seperti ini yang sedang direncanakan? Jika tidak, kemana saya akan memintanya?

NB. solusi javascript tidak baik untuk ini karena di seluler Anda biasanya hanya mendapatkan satu scrollperistiwa ketika pengguna melepaskan jarinya, jadi JS tidak dapat mengetahui saat yang tepat saat ambang batas gulir dilewati.

callum
sumber

Jawaban:

104

Saat ini tidak ada pemilih yang sedang diusulkan untuk elemen yang saat ini 'macet'. The postioned Tata Letak Modul mana position: stickydidefinisikan tidak menyebutkan pemilih tersebut baik.

Permintaan fitur untuk CSS dapat diposting ke milis www-style . Saya percaya :stuckpseudo-class lebih masuk akal daripada ::stuckpseudo-element, karena Anda ingin menargetkan elemen itu sendiri saat mereka dalam keadaan itu. Bahkan, :stuckkelas semu pernah dibahas beberapa waktu lalu ; Komplikasi utama, ditemukan, adalah salah satu yang mengganggu hampir semua pemilih yang diusulkan yang mencoba mencocokkan berdasarkan gaya yang diberikan atau dihitung: ketergantungan melingkar.

Dalam kasus :stuckpseudo-class, kasus sirkularitas paling sederhana akan terjadi dengan CSS berikut:

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

Dan mungkin ada lebih banyak kasus tepi yang sulit untuk ditangani.

Meskipun secara umum disepakati bahwa memiliki penyeleksi yang cocok berdasarkan status tata letak tertentu akan menyenangkan , sayangnya ada batasan utama yang membuat hal ini tidak sepele untuk diterapkan. Saya tidak akan menahan napas untuk solusi CSS murni untuk masalah ini dalam waktu dekat.

BoltClock
sumber
14
Itu memalukan. Saya juga sedang mencari solusi untuk masalah ini. Bukankah cukup mudah untuk hanya memperkenalkan aturan yang mengatakan positionproperti pada :stuckpemilih harus diabaikan? (aturan untuk vendor browser yang saya maksud, mirip dengan aturan tentang bagaimana leftmendahulukan, rightdll.))
powerbuoy
5
Ini bukan hanya posisi ... bayangkan a :stuckyang mengubah topnilainya dari 0menjadi 300px, lalu gulir ke bawah 150px... apakah itu melekat atau tidak? Atau pikirkan tentang elemen dengan position: stickydan di bottom: 0mana :stuckmungkin berubah font-sizedan oleh karena itu ukuran elemen (oleh karena itu mengubah momen di mana ia harus menempel) ...
Roman
3
Lihat github.com/w3c/csswg-drafts/issues/1660 di mana proposalnya adalah agar peristiwa JS mengetahui saat sesuatu macet / tidak macet. Itu seharusnya tidak memiliki masalah yang diperkenalkan oleh pseudo-selector.
Ruben
27
Saya percaya masalah melingkar yang sama dapat dibuat dengan banyak pseudo-class yang sudah ada (misalnya: hover mengubah lebar dan: tidak (: hover) berubah kembali). Saya akan senang: terjebak pseudo-class dan berpikir bahwa pengembang harus bertanggung jawab karena tidak memiliki masalah melingkar dalam kodenya.
Marek Lisý
12
Yah ... Saya tidak benar-benar memahami ini sebagai kesalahan - ini seperti mengatakan whilesiklus dirancang dengan buruk karena memungkinkan putaran tanpa akhir :) Namun terima kasih telah menyelesaikannya;)
Marek Lisý
26

Dalam beberapa kasus, yang sederhana IntersectionObserverdapat melakukan trik, jika situasinya memungkinkan untuk menempel pada satu atau dua piksel di luar wadah akarnya, daripada disiram dengan benar. Dengan cara itu ketika ia berada tepat di luar tepi, pengamat menembak dan kami pergi dan berlari.

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

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

Tempelkan elemen keluar dari wadahnya dengan top: -2px, lalu targetkan melalui stuckatribut ...

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

Contoh di sini: https://codepen.io/anon/pen/vqyQEK

dapat disiksa
sumber
1
Menurut saya stuckkelas akan lebih baik daripada atribut khusus ... Apakah ada alasan khusus untuk pilihan Anda?
collimarco
Kelas A juga berfungsi dengan baik, tetapi ini sepertinya levelnya sedikit lebih tinggi dari itu, karena ini adalah properti turunan. Sebuah atribut tampaknya lebih cocok untukku, tapi bagaimanapun juga itu masalah selera.
disiksa
Saya perlu bagian atas saya menjadi 60px karena tajuk yang sudah diperbaiki, jadi saya tidak bisa mendapatkan contoh Anda untuk berfungsi
FooBar
1
Coba tambahkan beberapa bantalan atas ke apa pun yang macet, mungkin padding-top: 60pxdalam kasus Anda :)
Tim Willis
5

Seseorang di blog Google Developers mengklaim telah menemukan solusi berbasis JavaScript yang performatif dengan IntersectionObserver .

Bit kode yang relevan di sini:

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

Saya sendiri belum mereplikasi, tetapi mungkin itu membantu seseorang tersandung pada pertanyaan ini.

neo post modern
sumber
3

Bukan penggemar menggunakan js hacks untuk hal-hal styling (yaitu getBoudingClientRect, scroll listening, resize listening), tapi begitulah cara saya memecahkan masalah saat ini. Solusi ini akan memiliki masalah dengan laman yang memiliki konten yang dapat diminimalkan / dimaksimalkan (<detail>), atau pengguliran bersarang, atau benar-benar bola kurva apa pun. Karena itu, ini adalah solusi sederhana ketika masalahnya juga sederhana.

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
sumber
Perhatikan bahwa pendengar acara gulir dijalankan di utas utama yang menjadikannya pembunuh kinerja. Gunakan Intersection Observer API sebagai gantinya.
Jule skeptis
if (requestedFrame) { return; }Ini bukan "pembunuh kinerja" karena pengelompokan bingkai animasi. Intersection Observer masih merupakan perbaikan.
Seph Reed
0

Cara ringkas saat Anda memiliki elemen di atas position:stickyelemen. Ini menetapkan atribut stuckyang dapat Anda cocokkan dengan CSSheader[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
sumber