CSS & JS Effect – Smooth scrollIntoView

Default scroll behaviour (jump)

先搭个场景

HTML

<body>
  <header style="height: 128px; background-color: lightgreen;">
    <ul>
      <li><a href="/src/home/home.html#hero-section">hero</a></li>
      <li><a href="/src/home/home.html#product-section">product</a></li>
      <li><a href="/src/home/home.html#service-section">service</a></li>
      <li><a href="/src/home/home.html#review-section">review</a></li>
    </ul>
  </header>
  <main>
    <section id="hero-section" style="height: 30vh; background-color: pink; scroll-margin-top: 64px;">
      hero
    </section>
    <section id="product-section" style="height: 30vh; background-color: lightblue; scroll-margin-top: 64px;">
      product
    </section>
    <section id="service-section" style="height: 150vh; background-color: lightgray; scroll-margin-top: 64px;">
      service
    </section>
    <section id="review-section" style="height: 30vh; background-color: lightcoral; scroll-margin-top: 64px;">
      review
    </section>
    <section id="review-section" style="height: 330vh; background-color: lightcoral; scroll-margin-top: 64px;">
      review 22
    </section>
  </main>
  <footer style="height: 500vh; background-color: lightgreen;">footer</footer>
  <script type="module" src="./home.ts"></script>
</body>

Styles

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;

  *:focus {
    outline: none;
  }
}

header,
section,
footer {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  font-size: 24px;
  padding-top: 24px;
}

header ul {
  display: flex;
  gap: 48px;
}
View Code

效果

by default,anchor + URL hash 会自动 scroll to target element,但是它是直接 jump scroll,而不是 smooth scroll。

 

Smooth scrollIntoView

我们使用 JavaScript DOM & BOM API 把 jump scroll 换成 smooth scroll。

const anchors = Array.from(document.querySelectorAll<HTMLAnchorElement>('a'));
const currentPage = window.location.origin + window.location.pathname;

for (const anchor of anchors) {
  const samePage = anchor.href.startsWith(currentPage);
  if (!samePage) continue;
  if (!anchor.href.includes('#')) continue;

  anchor.addEventListener('click', e => {
    const hash = new URL(anchor.href).hash;
    const elementToScroll = document.querySelector<HTMLElement>(hash);
    if (!elementToScroll) return;

    elementToScroll.scrollIntoView({ behavior: 'smooth' });

    e.preventDefault();
    window.history.pushState(null, '', anchor.href);
  });
}

最关键的一句是 scrollIntoView({ behavior: 'smooth' })。

behavior: 'smooth' 需要 IOS 16 才能支持

效果

 

scroll-margin-top & scrollIntoView center

by default anchor + URL hash scroll 或 scrollIntoView 都是 scroll to element 的头部

上面的 spacing 是 scroll-margin-top 造成的

如果没有加上这个 CSS Styles,那它会完全贴在最上面,不美观。

另外,我们也可以让它 scroll 去 element 的其它位置,比如 center, bottom

效果

有一点需要注意,不管 viewport 的高度是否比 element to scroll 大,只要我们指定去 center,那它就会 scroll to center,看上面 scroll to service section 的例子。

如果不喜欢这个体验,我们可以做一些小调整

const viewportHeight = window.innerHeight;
const elementToScrollHeight = elementToScroll.offsetHeight;
elementToScroll.scrollIntoView({
  behavior: 'smooth',
  block: viewportHeight > elementToScrollHeight ? 'center' : 'start',
});

先判断 viewport 和 element to scroll 的大小才决定 scroll to 'start' or 'center' 

效果

 

nested scroll

我们加多一个 div scroll,看看它的 scroll 体验是如何
<main style="height: 100vh; overflow: auto;">

效果

注:不管是用 scrollIntoView 或者 anchor + URL hash 都一样

两个 scrollbar 都会移动。

最上层的 scrollTop 会是 128px,也就是 scroll 到刚刚好碰到 main element,然后再继续 scroll main element。

 

冷知识 – 当 scroll-margin-top 遇上 overflow: hidden

参考:Stack Overflow – The scroll-margin-top property doesn't work when using a container with `overflow: hidden` [closed]

平常我们会使用 scroll-margin-top 让 anchor + URL hash scroll to element 时上头留一点 spacing,这样会比较美观。

但假如 element to scroll 刚巧设有 overflow hidden,那 sroll-margin-top 就会失效。

原因我不清楚,但破解之法是把 hidden 换成 clip。

当然这不是万能药,clip 和 hidden 是有区别的,clip 不能 scroll 只能隐藏,而且需要 IOS 16 才能支持。

如果真的避不开,那可能需要使用 scrollTo 替代 scrollIntoView 来实现了。

 

 

 

 

posted @ 2025-04-27 23:09  兴杰  阅读(63)  评论(0)    收藏  举报