Web 功能:
容器滚动状态查询
概览
阴影或渐变等视觉提示有助于用户了解他们可以滚动浏览更多内容。本指南介绍了如何使用 CSS container-scroll-state-queries 构建这些提示,以便根据容器的可滚动状态为元素设置样式,而无需依赖 JavaScript 滚动监听器或观察器。
实现
1. 建立滚动容器
滚动容器必须声明为滚动状态查询容器。
.scroller {
overflow-y: auto;
/* Establish this element as a scroll-state query container */
container-type: scroll-state;
position: relative;
}
2. 设置指示器的样式
将指示器元素(例如阴影、渐变或箭头)放置在容器内并设置其样式。默认情况下,这些控件不应显示。显示时,应通过设置 pointer-events: none 使其不可交互。
.indicator-top, .indicator-bottom {
position: sticky;
left: 0;
right: 0;
height: 20px;
opacity: 0;
transition: opacity 0.2s;
pointer-events: none; /* Let clicks pass through */
}
.indicator-top {
top: 0;
background: linear-gradient(to bottom, rgba(0,0,0,0.2), transparent); /* Example: Shadow */
}
.indicator-bottom {
bottom: 0;
background: linear-gradient(to top, rgba(0,0,0,0.2), transparent); /* Example: Shadow */
}
3. 查询滚动状态
将 @container 规则与 scroll-state 函数搭配使用。检查容器是否可向上或向下滚动,以显示相应的指示器。
/* Show top indicator when the user can scroll up */
@container scroll-state(scrollable: top) {
.indicator-top {
opacity: 1;
}
}
/* Show bottom indicator when the user can scroll down */
@container scroll-state(scrollable: bottom) {
.indicator-bottom {
opacity: 1;
}
}
后备策略
基本后备
如果不支持该功能,指示器将保持不可见状态。由于这些只是提示,对功能并不重要,因此在不支持的浏览器中省略它们是可以接受的。
高级后备(Intersection Observer)
如果需要提示,请使用 IntersectionObserver 在滚动条顶部和底部的哨兵元素移入和移出滚动条时切换类。
<!-- Sentinel elements placed at the ends of the scroller -->
<div class="sentinel-top"></div>
<!-- Content goes here -->
<div class="sentinel-bottom"></div>
/* Marker styling to ensure it does not affect layout */
.sentinel-top, .sentinel-bottom {
height: 0;
width: 0;
visibility: hidden;
}
.scroller.scrolled-down .indicator-top {
opacity: 1;
}
.scroller.can-scroll-down .indicator-bottom {
opacity: 1;
}
if (!CSS.supports('container-type', 'scroll-state')) {
const topSentinel = document.querySelector('.sentinel-top');
const bottomSentinel = document.querySelector('.sentinel-bottom');
const scroller = document.querySelector('.scroller');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.target === topSentinel) {
// If top sentinel is not intersecting, we have scrolled down
scroller.classList.toggle('scrolled-down', !entry.isIntersecting);
}
if (entry.target === bottomSentinel) {
// If bottom sentinel is intersecting, we reached the bottom
scroller.classList.toggle('can-scroll-down', !entry.isIntersecting);
}
});
}, { root: scroller });
observer.observe(topSentinel);
observer.observe(bottomSentinel);
}