타겟팅 위치 : 현재 '고정'상태에있는 고정 요소


110

position : sticky 는 이제 일부 모바일 브라우저에서 작동하므로 페이지와 함께 메뉴 막대를 스크롤 할 수 있지만 사용자가 스크롤 할 때마다 뷰포트 상단에 고정 할 수 있습니다.

하지만 현재 '고정'상태 일 때마다 고정 메뉴 막대의 스타일을 약간 변경하려면 어떻게해야합니까? 예를 들어, 페이지와 함께 스크롤 할 때마다 막대가 둥근 모서리를 갖기를 원할 수 있지만 뷰포트 상단에 달라 붙 자마자 상단 둥근 모서리를 제거하고 그 아래에 약간의 그림자를 추가 할 수 있습니다. 그것.

pseudoselector (예를 들어, 어떤 종류가 있습니까 ::stuck이 대상 요소) position: sticky 현재 숨어있다가? 아니면 브라우저 공급 업체가 파이프 라인에 이와 같은 것이 있습니까? 그렇지 않은 경우 어디에서 요청합니까?

NB. 모바일에서는 일반적으로 scroll사용자가 손가락을 뗄 때 하나의 이벤트 만 수신되므로 자바 스크립트 솔루션은 이에 적합하지 않습니다 . 따라서 JS는 스크롤 임계 값이 전달 된 정확한 순간을 알 수 없습니다.

답변:


104

현재 '고정 된'요소에 대해 제안되는 선택기가 없습니다. Postioned 레이아웃 모듈position: sticky 정의는 하나 그러한 선택을 언급하지 않습니다.

CSS에 대한 기능 요청은 www 스타일 메일 링리스트에 게시 할 수 있습니다 . 나는 :stuck가상 클래스가 ::stuck가상 요소 보다 더 합리적 이라고 생각하는데 , 그 상태에있는 동안 요소 자체를 대상으로하기 때문입니다. 사실, :stuck의사 클래스는 얼마 전에 논의 되었습니다 . 발견 된 주요 문제는 렌더링되거나 계산 된 스타일을 기반으로 일치를 시도하는 제안 된 선택자, 즉 순환 종속성을 괴롭히는 문제입니다.

a의 경우에는 :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/issues/1660을 참조 하세요 . JS 이벤트를 통해 무언가가 멈추거나 멈출 때를 알 수 있습니다. 의사 선택자가 도입하는 문제가 없어야합니다.
Ruben

27
이미 존재하는 많은 의사 클래스 (예 : : hover 변경 폭 및 : not (: hover) 다시 변경)에서 동일한 순환 문제가 발생할 수 있다고 생각합니다. 나는 : stuck pseudo-class를 좋아하고 개발자가 자신의 코드에서 순환 문제가 발생하지 않도록 책임 져야한다고 생각합니다.
Marek Lisý 2015 년

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당신의 경우 에 막혀있는 모든 것에 상단 패딩을 추가 하십시오 :)
Tim Willis

5

Google Developers 블로그의 누군가IntersectionObserver를 사용하여 수행 가능한 JavaScript 기반 솔루션을 찾았다 고 주장합니다 .

여기에 관련 코드 비트 :

/**
 * 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

스타일링 (예 : getBoudingClientRect, 스크롤 리스닝, 크기 조정 리스닝)에 js 해킹을 사용하는 팬은 아니지만 이것이 현재 문제를 해결하는 방법입니다. 이 솔루션은 최소화 / 최대화 가능한 콘텐츠 (<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;
    });
})

스크롤 이벤트 리스너는 메인 스레드에서 실행되므로 성능이 저하됩니다. 대신 Intersection Observer API를 사용하십시오.
회의적인 Jule

if (requestedFrame) { return; }애니메이션 프레임 일괄 처리로 인해 "성능 킬러"가 아닙니다. Intersection Observer는 여전히 개선되었습니다.
Seph Reed

0

요소 위에 요소가있을 때 간단한 방법입니다 position:sticky. stuckCSS에서 일치시킬 수 있는 속성 을 다음과 같이 설정합니다 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'))
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.