寻找一种现代的(类似于 "Observer",没有 "scroll" 事件)的方式来根据滚动方向添加正文 class

Looking for a modern(akin to an "Observer", without the "scroll" event) way to add body class based on scroll direction

最终目标是在用户向上滚动页面时向 body 元素添加 scrolling-up class 并在向下滚动时删除所述 class 。

我用老派的方式实现了这一点,通过将节流(通过 lodash)回调附加到 scroll 事件,如下所示:

var lastScrollTop = 0;

var checkScrollDirection = function() {
    var currentOffset = window.pageYOffset;
    document.body.classList.toggle('scrolling-up', currentOffset < lastScrollTop );
    lastScrollTop = currentOffset;
};

window.addEventListener('scroll', _.throttle(
        checkScrollDirection,
        100,
        {
            'leading': true,
            'trailing': true
        }
    )
);

这工作(非常)好,但我想知道是否可以通过使用现代 Observer 来实现这一点,因此将所有这些都从主线程中删除。即使受到限制,从纯逻辑的角度来看,上述逻辑仍然需要 CPU 多得多的时间。

谢谢!

如果我们在 body 中策略性地放置一些目标元素,我们可以让 IntersectionObserver 观察它们以感知 body 是向上滚动还是向下滚动。

此类目标的最小数量似乎是一个视口高度,每隔 100vh 下降一个 body(最后对剩余位进行调整)。这样一来,在任何时候视口中始终只有一个目标。这个目标可以被观察到,所以它会在不同的阈值触发代码。

在视口中 'stops' 的数量和观察所花费的时间之间需要取得平衡。此代码段每隔 5% 的视口进行一次观察,并且在尝试过的设备(笔记本电脑,iPad)上,这似乎在实践中没有出现明显的问题。

这种方法有点老套,因为它需要向文档中添加元素,并且在调整大小时,我们必须求助于 JS 来重新计算它们的数量、高度和位置。

但是,该方法似乎有效,我看到在狂躁的连续滚动和方向改变的情况下,绝对最大 GPU 使用率约为 7%。 'Ordinary' 几乎没有注册的滚动。片段中的目标宽度为 1px,我不知道让它们变宽或变薄是好是坏 processor-usage 明智。它们被放置在视口的中心,以防在边缘观察时出现任何问题。

此代码段只是将 class scrolling-up 添加到 body 中,而此 shows/hides 是一个固定的 header.

//Set up action on intersection being observed
let previousTarget, previousTop, scrollingUp;
const header = document.querySelector('.header');
function scrolledFn(entries) {
  entries.forEach(entry => {
    if (entry.isIntersecting) { //note, only one can intersect at a time
      if (entry.target == previousTarget) {
        const newScrolling = (entry.boundingClientRect.top > previousTop);
        if (newScrolling != scrollingUp) {
          document.body.classList.toggle('scrolling-up');
          scrollingUp = !scrollingUp;
        }
      }
      else {
        previousTarget = entry.target;
      }
      previousTop = entry.boundingClientRect.top;
    }
  });
}

const step = 0.05;
let thresholds =[];
for ( let t = 0; t <= 1; t += step) { thresholds.push(t); }
let observer = new IntersectionObserver(scrolledFn, {threshold: thresholds});

function setupTargets() {

// first remove any targets we may have already
const oldTargets = document.querySelectorAll('.observed');
  oldTargets.forEach(oldTarget => { oldTarget.remove();
});

// Insert targets into the document
const numWholeViewports = Math.floor(document.body.offsetHeight/window.innerHeight);
const numFullHeightTargets = Math.floor((numWholeViewports + 1) / 2);

let i = 0;
function createTarget(h) {
  const el = document.createElement('div');
  document.body.appendChild(el);
  observer.observe(el);
  el.classList.add('observed');
  el.style.top = i*200 + 'vh';
  el.style.height = h + 'vh';
}
for (i; i < numFullHeightTargets ; i++) {
  createTarget('100');
}
if (numWholeViewports%2 == 0) { createTarget((document.body.offsetHeight%window.innerHeight) * 100 / window.innerHeight); }

previousTop = 0;
scrollingUp = document.body.scrollTop == 0;
if (scrollingUp) document.body.classList.add('scrolling-up');
else document.body.classList.remove('scrolling-up');
}

window.onload = setupTargets;
window.onresize = setupTargets;
body {
  position: relative;
  width: 100vw;
  height: auto;
  overflow-y: auto;
}

.header {
  display: none;
  position: fixed;
  top: 0px;
  left: 0px;
  background-color: lime;
  width: 100%;
  height: 10%;
  align-items: center;
  justify-content: center;
  font-size: 4rem;
  z-index: 1;
}

.scrolling-up .header {
  display: flex;
}

.content {
  position: relative;
  width: 80vw;
  margin: 0 auto;
  top: 10%;
  padding: 10px 20px;
  font-size: 3rem;
}

.observed {
  width: 1px;
  position: absolute;
  left: 50%;
  margin: 0;
  padding: 0;
  border-width: 0;
  z-index: -99999;
}
<body>
<header class="header">HEADER</header>
<div class="content">
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam porttitor erat ut libero molestie, sit amet facilisis libero finibus. Vestibulum tincidunt, augue faucibus faucibus mattis, quam magna sollicitudin ante, sed hendrerit nisl dolor sit amet neque. Integer feugiat malesuada lobortis. Quisque accumsan, nulla efficitur sodales eleifend, purus arcu lacinia nunc, id consequat mauris nisi ultricies lacus. Cras finibus commodo ipsum, in dictum mauris molestie in. Maecenas pretium ipsum ac velit porttitor, eu sagittis sapien vehicula. Donec vulputate urna non dui egestas iaculis.
</p><p>
Etiam sit amet eros in purus venenatis tristique. Donec vel tortor facilisis, tempus nibh a, consectetur velit. Aenean suscipit lacus diam, et ultricies nunc interdum quis. Nam orci sem, hendrerit sit amet lectus nec, convallis facilisis erat. Duis blandit nibh neque, quis porta mauris consectetur sit amet. Nam porttitor dolor vel euismod porttitor. Cras commodo tristique nunc. Proin ultrices sed odio et elementum. Praesent ex dui, placerat sed libero sed, consectetur pellentesque erat. Quisque volutpat molestie nisi eget tristique.
</p><p>
Nam sapien mi, mollis eu scelerisque sit amet, tristique eu purus. In quis feugiat massa. Suspendisse ac tellus neque. Vivamus risus nisl, posuere id sem id, aliquet semper nibh. Sed elementum facilisis bibendum. Maecenas ac nunc placerat lectus ultrices sodales. Nunc nec augue purus. Vestibulum a molestie lacus.
</p><p>
Curabitur ut tortor dolor. Suspendisse semper, leo et luctus laoreet, odio magna sagittis elit, sed bibendum risus lacus ut metus. Nulla a lobortis massa. Pellentesque volutpat iaculis faucibus. Integer vel erat sed orci lobortis rhoncus ornare ut purus. Phasellus rutrum varius rutrum. Curabitur fermentum finibus tortor at placerat. Pellentesque cursus nibh in dolor dictum tristique. Fusce auctor sapien libero, et porta sem pulvinar eu. Praesent lobortis lacus eget lacus fringilla posuere. Mauris vehicula tortor ut elit tincidunt luctus.
</p><p>
Pellentesque porttitor id nulla vitae auctor. Nam orci urna, molestie nec lorem sed, porttitor pulvinar erat. Proin magna sapien, molestie a ipsum eget, iaculis ornare ipsum. Cras imperdiet purus sed sapien sodales sodales. Nam dui nulla, ornare id ornare vel, placerat scelerisque velit. Nunc non dignissim orci. Aliquam diam massa, hendrerit at consectetur eu, eleifend vestibulum erat. Fusce tincidunt eget dolor at faucibus. Donec euismod elementum tellus, eu tristique massa malesuada vel. Aenean sit amet enim id elit sollicitudin dictum.
</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam porttitor erat ut libero molestie, sit amet facilisis libero finibus. Vestibulum tincidunt, augue faucibus faucibus mattis, quam magna sollicitudin ante, sed hendrerit nisl dolor sit amet neque. Integer feugiat malesuada lobortis. Quisque accumsan, nulla efficitur sodales eleifend, purus arcu lacinia nunc, id consequat mauris nisi ultricies lacus. Cras finibus commodo ipsum, in dictum mauris molestie in. Maecenas pretium ipsum ac velit porttitor, eu sagittis sapien vehicula. Donec vulputate urna non dui egestas iaculis.
</p>
</div>

请注意,在 IOS 上可能会出现轻微的 'edge' 滚动回最顶部的情况 - 需要调查 - 可能与 100vh 相关,而不是有导航栏时的视口高度在浏览器的顶部。