سرایندهای چسبان آگاه از وضعیت

سرایندهای چسبان یک الگوی رایج رابط کاربری هستند، اما اغلب نیاز دارند که ظاهر خود را زمانی که «چسبیده» می‌شوند تغییر دهند تا خوانایی حفظ شود یا فضا صرفه‌جویی شود. راه‌حل‌های سنتی به شنوندگان پیمایش جاوا اسکریپت نیاز داشتند که می‌تواند باعث مشکلات عملکرد شود. CSS مدرن به شما امکان می‌دهد این کار را به‌طور بومی بااستفاده از پرس‌وجوهای وضعیت پیمایش انجام دهید.

مراحل پیاده‌سازی

۱. ایجاد کردن ظرف چسبان

به محفظه‌ای نیاز دارید که به‌عنوان عنصر چسبنده و محفظه برای پُرسمان وضعیت پیمایش عمل کند.

<!-- 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>

۲. اعمال 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 خودش سبک‌بندی کنید.

۳. هدف قرار دادن وضعیت گیرکرده

از پُرسمان @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)، اما به‌صورت دیداری تغییر نمی‌کند. برای اکثر موارد استفاده، این رویکرد توصیه می‌شود.

اگر چسبندگی ضروری نیست، اما 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) محدود کنید.

اگر تبدیل بصری برای طراحی کاملاً حیاتی است (مثلاً حالت گیرکرده پس‌زمینه یا فشرده‌سازی را معرفی می‌کند که بدون آن محتوا غیرقابل‌خواندن است)، می‌توانید بااستفاده از IntersectionObserver یک بازگشت جاوا اسکریپت قوی پیاده‌سازی کنید. باید سبک‌های CSS خود را تحت کلاس .is-stuck تکرار کنید. شبیه‌ساز مرورگر عمومی زیر 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 می‌توان به‌عنوان شبیه‌ساز مرورگر برای پُرسمان scroll-state(scrollable) نیز استفاده کرد.