ตำแหน่งการกำหนดเป้าหมาย: องค์ประกอบเหนียวที่อยู่ในสถานะ 'ติด'


111

ตำแหน่ง: เหนียวใช้งานได้กับเบราว์เซอร์มือถือบางรุ่นในขณะนี้ดังนั้นคุณสามารถเลื่อนแถบเมนูไปพร้อมกับหน้าได้ แต่จะติดอยู่ที่ด้านบนของวิวพอร์ตเมื่อใดก็ตามที่ผู้ใช้เลื่อนผ่าน

แต่จะเกิดอะไรขึ้นถ้าคุณต้องการปรับสไตล์แถบเมนูเหนียวของคุณเล็กน้อยเมื่อใดก็ตามที่ 'ติด' อยู่? เช่นคุณอาจต้องการให้แถบมีมุมโค้งมนเมื่อใดก็ตามที่เลื่อนไปพร้อมกับหน้า แต่ทันทีที่มันเกาะอยู่ที่ด้านบนสุดของวิวพอร์ตคุณจะต้องกำจัดมุมที่โค้งมนด้านบนออกและเพิ่มเงาเล็กน้อยด้านล่าง มัน.

มี pseudoselector ชนิดใด (เช่น::stuck) เพื่อกำหนดเป้าหมายองค์ประกอบที่มีposition: sticky และกำลังเกาะอยู่หรือไม่? หรือผู้ขายเบราว์เซอร์มีอะไรเช่นนี้ในท่อหรือไม่? ถ้าไม่ฉันจะขอได้ที่ไหน

NB. โซลูชัน javascript ไม่ดีสำหรับสิ่งนี้เนื่องจากบนมือถือคุณมักจะได้รับscrollเหตุการณ์เดียวเมื่อผู้ใช้ปล่อยนิ้วดังนั้น JS จึงไม่สามารถทราบช่วงเวลาที่แน่นอนที่เกณฑ์การเลื่อนผ่าน

คำตอบ:


104

ขณะนี้ไม่มีตัวเลือกที่ถูกเสนอสำหรับองค์ประกอบที่ 'ค้าง' อยู่ในขณะนี้ โมดูล Postioned เค้าโครงที่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/issues/1660ซึ่งข้อเสนอคือให้มีเหตุการณ์ JS เพื่อให้ทราบเมื่อมีบางสิ่งติดขัด / ไม่ติดขัด ที่ไม่ควรมีปัญหาที่ตัวเลือกหลอกแนะนำ
Ruben

27
ฉันเชื่อว่าปัญหาแบบวงกลมเดียวกันสามารถเกิดขึ้นได้กับคลาสหลอกที่มีอยู่แล้วจำนวนมาก (เช่น: เลื่อนเมาส์ไปเปลี่ยนความกว้างและ: ไม่ (: โฮเวอร์) เปลี่ยนกลับอีกครั้ง) ฉันชอบ: ติดหลอกคลาสและคิดว่านักพัฒนาควรรับผิดชอบที่ไม่มีปัญหาวงกลมในโค้ดของเขา
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

ชั้นเรียนก็ใช้ได้เช่นกัน แต่ดูเหมือนว่าระดับนี้จะสูงกว่านั้นเล็กน้อยเนื่องจากเป็นคุณสมบัติที่ได้รับ แอตทริบิวต์ดูเหมือนจะเหมาะสมกับฉันมากกว่า แต่ไม่ว่าจะด้วยวิธีใดก็เป็นเรื่องของรสนิยม
แร็คได้

ฉันต้องการให้ส่วนบนของฉันเป็น 60px เนื่องจากส่วนหัวคงที่แล้วดังนั้นฉันจึงไม่สามารถทำให้ตัวอย่างของคุณใช้งานได้
FooBar

1
ลองเพิ่มช่องว่างด้านบนให้กับสิ่งที่ติดอยู่บางทีpadding-top: 60pxในกรณีของคุณ :)
Tim Willis

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 hacks ในการจัดแต่งทรงผม (เช่น 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;
    });
})

โปรดทราบว่าตัวฟังเหตุการณ์การเลื่อนถูกเรียกใช้งานบนเธรดหลักซึ่งทำให้เป็นตัวฆ่าประสิทธิภาพ ใช้ Intersection Observer API แทน
Jule สงสัย

if (requestedFrame) { return; }ไม่ใช่ "ตัวทำลายประสิทธิภาพ" เนื่องจากการจัดกลุ่มเฟรมภาพเคลื่อนไหว Intersection Observer ยังคงปรับปรุงอยู่
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'))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.