ส่วนหัวแบบติดหนึบเป็นรูปแบบ UI ที่พบได้ทั่วไป แต่มักจะต้องเปลี่ยนรูปลักษณ์เมื่อ "ติดหนึบ" เพื่อให้อ่านง่ายหรือประหยัดพื้นที่ โซลูชันแบบเดิมต้องใช้ JavaScript Scroll Listener ซึ่งอาจทำให้เกิดปัญหาด้านประสิทธิภาพ CSS สมัยใหม่ช่วยให้คุณจัดการเรื่องนี้ได้โดยตรงโดยใช้การค้นหาสถานะการเลื่อน
ขั้นตอนการติดตั้งใช้งาน
1. สร้างคอนเทนเนอร์แบบติดหนึบ
คุณต้องมีคอนเทนเนอร์ที่จะทำหน้าที่เป็นองค์ประกอบติดหนึบและคอนเทนเนอร์สำหรับการค้นหาสถานะการเลื่อน
<!-- Wrap in a section to define the containment area for the sticky element -->
<div class="section">
<div class="sticky-container">
<div class="sticky-header">
Section Header
</div>
</div>
<div class="content">
<!-- Content goes here -->
</div>
</div>
2. ใช้ CSS สำหรับลักษณะการทำงานแบบติดหนึบและประเภทคอนเทนเนอร์
ตั้งค่า container-type: scroll-state ในองค์ประกอบ position: sticky ไม่ใช่ในระดับบนสุดที่เลื่อนได้ ระบุ container-name เสมอ เนื่องจากคำค้นหาเหล่านี้อาจซ้อนกันและคอนเทนเนอร์ที่ไม่มีชื่อจะชนกัน นอกจากนี้ คุณยังรวมคำค้นหาประเภทนี้เข้ากับคำค้นหาขนาดได้ด้วย เช่น container-type: scroll-state inline-size
.sticky-container {
position: sticky;
top: 0;
/* MANDATORY: Always specify a container name to avoid collisions if CQs are nested */
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
.sticky-header {
/* Base styles for the header */
background: #f0f0f0;
padding: 20px;
transition: background-color 0.3s, box-shadow 0.3s;
}
สำคัญ: สถานะการเลื่อนจะค้นหาโดยองค์ประกอบสืบทอดของคอนเทนเนอร์สถานะการเลื่อน รูปแบบในการค้นหา @container จะไม่มีผลกับ .sticky-container เอง แต่จะมีผลกับองค์ประกอบย่อย (เช่น .sticky-header) คุณไม่สามารถจัดรูปแบบองค์ประกอบคอนเทนเนอร์ด้วยการค้นหา scroll-state ขององค์ประกอบเอง
3. กำหนดเป้าหมายสถานะติดขัด
ใช้การค้นหา @container scroll-state(...) เพื่อใช้รูปแบบเมื่อส่วนหัวติดอยู่
/* Apply styles when the named container is stuck at the top */
@container section-header scroll-state(stuck: top) {
.sticky-header {
background: #0056b3;
color: white;
box-shadow: 0 4px 6px rgb(0 0 0 / 0.15);
}
}
เคล็ดลับ: คุณยังใช้พร็อพเพอร์ตี้เชิงตรรกะ เช่น stuck: inset-block-start หรือ stuck: inset-inline-start เพื่อรองรับการปรับให้เหมาะกับแต่ละประเทศ (i18n) และเลย์เอาต์จากขวาไปซ้าย (RTL) ได้ดียิ่งขึ้นด้วยการค้นหาขอบที่สัมพันธ์กับโฟลว์แทนขอบทางกายภาพ
หมายเหตุเกี่ยวกับการเปลี่ยนแปลงมิติข้อมูล
หากไม่ได้ปิดใช้การตรึงการเลื่อน การเปลี่ยนพร็อพเพอร์ตี้ที่ส่งผลต่อเลย์เอาต์ (ความสูง, ระยะห่างจากขอบ, ขนาดแบบอักษร) เมื่อติดขัดอาจทำให้ภาพกะพริบ การตรึงการเลื่อนในเนื้อหาในโฟลว์ที่อยู่ใต้องค์ประกอบแบบติดหนึบจะปรับออฟเซ็ตการเลื่อนเพื่อชดเชยการเปลี่ยนแปลงเลย์เอาต์ ซึ่งจะดันองค์ประกอบกลับออกจากตำแหน่งที่ติดหนึบและทำให้เกิดการสั่น
ปิดใช้การตรึงการเลื่อนในองค์ประกอบหลักขององค์ประกอบที่ติดหนึบเพื่อหลีกเลี่ยงปัญหานี้ โดยทำดังนี้
.section {
overflow-anchor: none;
}
ใช้ overflow-anchor: none กับองค์ประกอบหลักโดยตรงขององค์ประกอบ position: sticky ไม่ใช่ :root หรือ body ซึ่งจะปิดใช้การตรึงการเลื่อนสำหรับทั้งหน้า เมื่อใช้ฟีเจอร์นี้ คุณจะเปลี่ยนพร็อพเพอร์ตี้ใดก็ได้ในสถานะติดหนึบได้อย่างอิสระ รวมถึงพร็อพเพอร์ตี้โมเดลกล่องและการเปลี่ยนรูปแบบ
อย่าใช้ overflow-anchor: none สำหรับองค์ประกอบที่ติดอยู่ด้านล่าง การตรึงการเลื่อนจะชดเชยเฉพาะการเปลี่ยนเลย์เอาต์ที่อยู่เหนือตำแหน่งการเลื่อนปัจจุบันเท่านั้น จึงไม่มีผลกับองค์ประกอบที่ติดอยู่ด้านล่าง หลีกเลี่ยงการเปลี่ยนพร็อพเพอร์ตี้โมเดลกล่องในสถานะติดค้างสำหรับส่วนหัวที่ติดค้างด้านล่าง
กลยุทธ์สำรอง
การค้นหาสถานะการเลื่อนเป็นการเพิ่มประสิทธิภาพแบบต่อเนื่อง ในเบราว์เซอร์ที่ไม่รองรับ container-type: scroll-state ส่วนหัวจะยังคงติดอยู่ที่ด้านบน (เนื่องจาก position: sticky) แต่จะไม่เปลี่ยนรูปแบบในเชิงภาพ ซึ่งเป็นแนวทางที่แนะนำสำหรับ Use Case ส่วนใหญ่
หากความเหนียวแน่นไม่จำเป็น แต่ position: sticky ที่ไม่มีการจัดรูปแบบที่แตกต่างกันจะทำให้ประสบการณ์การใช้งานแย่กว่าการไม่มีความเหนียวแน่นเลย คุณสามารถรวม position: sticky และการประกาศที่เกี่ยวข้องไว้ในคำค้นหา @supports ได้ด้วย ดังนี้
.sticky-container {
@supports (container-type: scroll-state) {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
}
เคล็ดลับ: หากการจัดรูปแบบ "ติดหนึบ" ต้องใช้สีพื้นหลังอื่นเพื่อให้อ่านง่าย ให้ลองตั้งค่าเลย์เอาต์เพื่อให้การจัดรูปแบบเริ่มต้นใช้งานได้ทุกที่ (การเพิ่มประสิทธิภาพแบบต่อเนื่อง) หากต้องใช้โฆษณาสำรอง คุณสามารถจำกัดลักษณะการทำงานของ position: sticky ไว้ภายในคำค้นหา @supports (container-type: scroll-state)
หากการเปลี่ยนรูปแบบภาพมีความสำคัญอย่างยิ่งต่อการออกแบบ (เช่น สถานะติดค้างทำให้เกิดพื้นหลังหรือการบีบอัดซึ่งทำให้เนื้อหาอ่านไม่ได้) คุณสามารถใช้ JavaScript สำรองที่มีประสิทธิภาพโดยใช้ IntersectionObserver คุณต้องทำซ้ำสไตล์ CSS ในคลาส .is-stuck Polyfill ทั่วไปต่อไปนี้จะตรวจสอบ getComputedStyle เพื่อข้ามขึ้นไปและค้นหาคอนเทนเนอร์การเลื่อนที่ถูกต้อง (ค่าเริ่มต้นคือวิวพอร์ต) ซึ่งตรงกับลักษณะการทำงานของ @container scroll-state(stuck: top)
function getScrollParent(node) {
if (node == null || node === document.body || node === document.documentElement) {
return null; // default to viewport
}
if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) {
const overflow = getComputedStyle(node).overflow;
if (overflow !== 'visible' && overflow !== 'clip') {
return node;
}
}
return getScrollParent(node.parentNode);
}
document.querySelectorAll('.sticky-container').forEach(container => {
const root = getScrollParent(container);
const topOffset = parseFloat(getComputedStyle(container).top) || 0;
const observer = new IntersectionObserver(
([e]) => {
// Toggle the fallback class on the sticky header container
e.target.classList.toggle('is-stuck', e.intersectionRatio < 1);
},
{
root: root,
threshold: [1],
rootMargin: `-${topOffset + 1}px 0px 0px 0px`
}
);
observer.observe(container);
});
หมายเหตุ: รูปแบบ IntersectionObserver ทั่วไปนี้ยังใช้เป็น Polyfill สำหรับการค้นหา scroll-state(scrollable) ได้ด้วย