寻找一种现代类似于“观察者”,没有“滚动”事件的方式来添加基于滚动方向的主体类

问题描述

最终目标是在用户向上滚动页面时向 scrolling-up 元素添加一个 body 类,并在向下滚动时移除该类。

我正在以老派的方式实现这一目标,方法是将节流(通过 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 沿身体向下移动(最后调整剩余位)。这样一来,视口中始终只有一个,但只有一个目标。可以观察到该目标,因此它会在各种阈值下触发代码。

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

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

但是,该方法似乎有效,我在狂躁的连续滚动和方向改变中看到的绝对最大 GPU 使用率约为 7%。 “普通”类型的滚动很难注册。代码片段中的目标宽度为 1px,我不知道让它们宽还是窄是更好还是更差处理器使用明智。它们被放置在视口的中心,以防万一在边缘观察时出现任何问题。

此代码段只是将类向上滚动添加到适当的正文中,这会显示/隐藏固定的标题。

//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,eu sagittis sapien vehicula. Donec vulputate urna non dui egestas iaculis.
</p>
</div>

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