Vị trí nhắm mục tiêu: các yếu tố cố định hiện đang ở trạng thái 'bị kẹt'


109

position: stick hiện hoạt động trên một số trình duyệt dành cho thiết bị di động, vì vậy bạn có thể tạo thanh menu cuộn theo trang nhưng sau đó dính vào đầu khung nhìn bất cứ khi nào người dùng cuộn qua nó.

Nhưng điều gì sẽ xảy ra nếu bạn muốn thiết kế lại thanh menu dính của mình một chút bất cứ khi nào nó hiện đang 'dính'? ví dụ: bạn có thể muốn thanh có các góc tròn bất cứ khi nào nó cuộn với trang, nhưng sau đó ngay khi nó dính vào đầu khung nhìn, bạn muốn loại bỏ các góc tròn trên cùng và thêm một chút bóng đổ bên dưới nó.

Có bất kỳ loại trình dò ​​giả nào (ví dụ ::stuck) để nhắm mục tiêu các phần tử đã position: sticky đang gắn bó không? Hay các nhà cung cấp trình duyệt có bất kỳ thứ gì tương tự như thế này trong đường dẫn không? Nếu không, tôi sẽ yêu cầu nó ở đâu?

NB. Các giải pháp javascript không tốt cho điều này vì trên thiết bị di động, bạn thường chỉ nhận được một scrollsự kiện duy nhất khi người dùng thả ngón tay của họ, vì vậy JS không thể biết chính xác thời điểm mà ngưỡng cuộn được vượt qua.

Câu trả lời:


104

Hiện không có bộ chọn nào được đề xuất cho các phần tử hiện đang bị 'mắc kẹt'. Các mô-đun Postioned Layout nơiposition: sticky được xác định không đề cập đến bất kỳ chọn như một trong hai.

Yêu cầu tính năng cho CSS có thể được đăng vào danh sách gửi thư kiểu www . Tôi tin rằng :stucklớp giả có ý nghĩa hơn là ::stuckphần tử giả, vì bạn đang muốn nhắm mục tiêu chính các phần tử khi chúng ở trạng thái đó. Trên thực tế, một :stucklớp học giả đã được thảo luận một thời gian trước ; phức tạp chính, nó đã được tìm thấy, là một sự phức tạp xảy ra đối với bất kỳ bộ chọn được đề xuất nào cố gắng so khớp dựa trên kiểu được kết xuất hoặc tính toán: phụ thuộc vòng tròn.

Trong trường hợp :stucklớp giả, trường hợp lưu hành đơn giản nhất sẽ xảy ra với CSS sau:

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

Và có thể có nhiều trường hợp khác khó giải quyết.

Mặc dù người ta thường đồng ý rằng có các bộ chọn phù hợp dựa trên các trạng thái bố cục nhất định sẽ là tốt , nhưng tiếc là có những hạn chế lớn khiến những hạn chế này không thể triển khai được. Tôi sẽ không nín thở để có một giải pháp CSS thuần túy cho vấn đề này sớm.


14
Thật là sự xấu hổ. Tôi cũng đang tìm kiếm một giải pháp cho vấn đề này. Sẽ không khá dễ dàng nếu chỉ giới thiệu một quy tắc nói rằng positioncác thuộc tính trên một :stuckbộ chọn nên bị bỏ qua? (Ý tôi là một quy tắc dành cho các nhà cung cấp trình duyệt, tương tự như các quy tắc về cách leftđược ưu tiên hơn so với rightv.v.))
powerbuoy

5
Nó không chỉ là vị trí ... hãy tưởng tượng một :stuckthay đổi topgiá trị từ 0thành 300px, sau đó cuộn xuống 150px... nó có nên dính hay không? Hoặc suy nghĩ về một phần tử với position: stickybottom: 0nơi :stuckcó thể thay đổi font-sizevà do đó các yếu tố kích thước (do thay đổi thời điểm, trong đó nó nên dính) ...
La Mã

3
Xem github.com/w3c/csswg-drafts/issues/1660 trong đó đề xuất có các sự kiện JS để biết khi nào có điều gì đó bị kẹt / không hoạt động. Điều đó không nên có các vấn đề mà bộ chọn giả giới thiệu.
Ruben

27
Tôi tin rằng các vấn đề vòng tròn tương tự có thể được thực hiện với nhiều lớp giả đã tồn tại (ví dụ: di chuột thay đổi chiều rộng và: không (: hover) thay đổi lại). Tôi rất thích: bị mắc kẹt trong lớp giả và nghĩ rằng nhà phát triển phải chịu trách nhiệm về việc không có các vấn đề về vòng tròn trong mã của mình.
Marek Lisý

12
Vâng ... Tôi không thực sự hiểu điều này như sai lầm - nó giống như câu nói whilechu kỳ được thiết kế tồi tệ vì nó cho phép vòng lặp vô tận :) Tuy nhiên nhờ cho thanh toán bù trừ này;)
Marek Lisý

25

Trong một số trường hợp, một đơn giản IntersectionObservercó thể thực hiện thủ thuật, nếu tình huống cho phép dính vào một hoặc hai pixel bên ngoài vùng chứa gốc của nó, thay vì xả ngược lại đúng cách. Theo cách đó, khi nó nằm ngay bên ngoài rìa, người quan sát phát hỏa và chúng ta đang chạy.

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

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

Chỉ dính phần tử ra khỏi vùng chứa của nó với top: -2px, sau đó nhắm mục tiêu qua stuckthuộc tính ...

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

Ví dụ ở đây: https://codepen.io/anon/pen/vqyQEK


1
Tôi nghĩ rằng một stucklớp sẽ tốt hơn một thuộc tính tùy chỉnh ... Có lý do cụ thể nào cho lựa chọn của bạn không?
collimarco

Một lớp cũng hoạt động tốt, nhưng mức này có vẻ cao hơn một chút so với mức đó, vì nó là một thuộc tính dẫn xuất. Một thuộc tính có vẻ thích hợp hơn với tôi, nhưng theo cách nào thì đó là vấn đề về sở thích.
Rackable

Tôi cần phần trên cùng của mình là 60px vì tiêu đề đã cố định, vì vậy tôi không thể làm cho ví dụ của bạn hoạt động
FooBar

1
Hãy thử thêm một số phần đệm trên cùng vào bất cứ thứ gì đang bị kẹt, có thể padding-top: 60pxtrong trường hợp của bạn :)
Tim Willis

5

Một người nào đó trên blog Nhà phát triển của Google tuyên bố đã tìm thấy giải pháp dựa trên JavaScript hiệu quả với IntersectionObserver .

Bit mã liên quan ở đây:

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

Tôi đã không tự sao chép nó, nhưng có thể nó sẽ giúp ai đó vấp phải câu hỏi này.


3

Không hẳn là người thích sử dụng js hacks để tạo kiểu (ví dụ: getBoudingClientRect, nghe cuộn, nghe thay đổi kích thước), nhưng đây là cách tôi hiện đang giải quyết vấn đề. Giải pháp này sẽ gặp vấn đề với các trang có nội dung có thể thu nhỏ / tối đa hóa (<details>), hoặc cuộn lồng nhau, hoặc thực sự là bất kỳ đường cong nào. Điều đó đang được nói, đó là một giải pháp đơn giản khi vấn đề cũng đơn giản.

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

Lưu ý rằng trình nghe sự kiện cuộn được thực thi trên luồng chính khiến nó trở thành kẻ giết hiệu suất. Thay vào đó, hãy sử dụng API trình quan sát giao lộ.
Skeptical Jule

if (requestedFrame) { return; }Nó không phải là một "kẻ giết người về hiệu suất" do sự phân phối khung hình hoạt hình. Mặc dù vậy, Intersection Observer vẫn là một cải tiến.
Reed

0

Một cách nhỏ gọn cho khi bạn có một phần tử ở trên position:stickyphần tử. Nó đặt thuộc tính stuckmà bạn có thể so khớp trong CSS với 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'))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.