CSS Scroll Snap 获取活动项目

CSS Scroll Snap get active Item

我对 CSS scroll snap 有疑问。我想通过 JavaScript 检测捕捉到的元素并分配它,例如 CSS class 或类似的。

遗憾的是,我还没有找到一种方法来检测捕捉到的元素。背景:我有一个包含子项的列表,它是滚动的,列表中的中间项总是应该突出显示:

布局

我已经用 rootMargin 测试了 intersection observer 来检测垂直居中的元素,但它的错误多于有用。

HTML

<div class="timeline-menu-dropdown-years-list-container">
    <ul class="timeline-menu-dropdown-years-list timeline-menu-dropdown-years-text" id="yearcontainer">
        <li id="2010" class="timeline-dropdown-year" data-target="year-2010">2010</li>
        <li id="2009" class="timeline-dropdown-year" data-target="year-2009">2009</li>
        <li id="2008" class="timeline-dropdown-year" data-target="year-2008">2008</li>
        <li id="2007" class="timeline-dropdown-year" data-target="year-2007">2007</li>
        <li id="2006" class="timeline-dropdown-year" data-target="year-2006">2006</li>
        <li id="2005" class="timeline-dropdown-year" data-target="year-2005">2005</li>
        <li id="2004" class="timeline-dropdown-year" data-target="year-2004">2004</li>
        <li id="2003" class="timeline-dropdown-year" data-target="year-2003">2003</li>
        <li id="2002" class="timeline-dropdown-year" data-target="year-2002">2002</li>
        <li id="2001" class="timeline-dropdown-year" data-target="year-2001">2001</li>
        <li id="2000" class="timeline-dropdown-year" data-target="year-2000">2000</li>
    </ul>
</div>

CSS

.timeline-menu-dropdown-years-list-container {
  max-height: 250px;
  overflow: scroll;
  scroll-snap-type: y mandatory;
  -ms-overflow-style: none;  /* Internet Explorer and Edge */
  scrollbar-width: none;  /* Firefox */
  padding-top: 45%;
  padding-bottom: 40%;
  scroll-padding-top: 45%;
  scroll-padding-bottom: 40%;
}

.timeline-dropdown-year {
  color: white;
  font-size: 14px;
  border-bottom: 2px solid white;
  margin-right: 11%;
  margin-left: 34%;
  scroll-snap-align: center;
}

我该如何解决?

最后,您应该可以滚动浏览此时间线。活动元素应始终对齐中心并在视觉上突出显示。

我也遇到了同样的问题。我在这里用 JavaScript 解决了它:Implementation of CSS scroll snap event stop and element position detection

[].slice.call(container.children).forEach(function (ele, index) {
    if (Math.abs(ele.getBoundingClientRect().left - container.getBoundingClientRect().left) < 10) {
        // The 'ele' element at this moment is the element currently
        // positioned. Add class .active for example!

    } else {
        // The 'ele' element at the moment is not
        // the currently positioned element
    }
});

将其放入事件滚动条中:

// Timer, used to detect whether horizontal scrolling is over
var timer = null;
// Scrolling event start
container.addEventListener('scroll', function () {
    clearTimeout(timer);
    // Renew timer
    timer = setTimeout(function () {
        // No scrolling event triggered. It is considered that
        // scrolling has stopped do what you want to do, such
        // as callback processing
    }, 100);
});

对当前答案的改进是预先存储每个元素的边界,以防止每次触发滚动事件时回流。

let container = document.querySelector('.timeline-menu-dropdown-years-list-container')
let containerBounds = null
let currentItem = 0

// store items as an array of objects
const items = Array.from(document.querySelectorAll('.timeline-dropdown-year')).map(el => ({el}))

const storeBounds = () =>{
    // store the bounds of the container
    containerBounds = container.getBoundingClientRect() // triggers reflow
    // store the bounds of each item
    items.forEach((item, i)=>{
        item.bounds = item.el.getBoundingClientRect() // triggers reflow
        item.offsetY = item.bounds.top - containerBounds.top // store item offset distance from container
    })
}
storeBounds() // store bounds on load

const detectCurrent = () => {
    const scrollY = container.scrollTop // container scroll position
    const goal = container.bounds.height / 2 // where we want the current item to be, 0 = top of the container
    // find item closest to the goal
    currentItem = items.reduce((prev, curr) => {
        return (Math.abs(curr.offsetY - scrollY - goal) < Math.abs(prev.offsetY - scrollY - goal) ? curr : prev); // return the closest to the goal
    });
    
    // do stuff with currentItem
    // here
    
}
detectCurrent() // detect current item on load

window.addEventListener('scroll', () => detectCurrent()) // detect current item on scroll
window.addEventListener('resize', () => storeBounds()) // update bounds on resize in case they have changed

进一步的改进是在滚动事件上有一个 debounce 函数,以减少计算的频率。我们真的不需要在每次触发滚动事件时都进行检查,并且可以在每次检查之间设置大约 200 毫秒的间隔。

根据布局是否更改,您可能还需要在调整页面大小时更新边界。

不熟悉浏览器重排工作原理的资源: