发布于 2026-01-06 0 阅读
0

在 Svelte 中构建 Headroom 风格的头部布局 方案:固定或取消固定 使用 Svelte 状态 滚动检测 响应式声明 类名选择? 全部内容

在 Svelte 中构建一个带顶棚的过梁

布局

计划:固定还是取消固定

使用 Svelte 状态

滚动检测

响应式声明

课程名称是什么?

全部

让我们Svelte构建一个带有头部空间的页眉!本博客文章的目标是创建一个页眉,当用户向下滚动页面时,页眉会向上滑动(并移出屏幕),而当用户向上滚动页面时,页眉会重新出现(无论用户滚动到页面的哪个位置)。

这是一种节省屏幕空间的技术,同时还能避免用户必须向上滚动页面才能看到页眉和导航栏。

我们不会使用流行的headroom.js,而是自己开发一个简单的解决方案,同时在这个过程中磨练我们的 Svelte 技能。准备好了吗?

布局

我们将从一个带有fixed头部信息的组件开始,就像它已经被“固定”了一样。让我们给头部信息添加一个 `<header>` 标签heightbackground-color这样我们就能真正看到它了。我们的 Svelte 组件正式亮相:

<style>
 header {
    background-color: darkgrey;
    height: 80px;
    position: fixed;
    width: 100%;
  }
  main {
    min-height: 150vh;
    padding-top: 80px;
  }
</style>

<header />
<main>Lorem ipsum</main>

你可以看到,我们给main标签设置了一个与头部padding-top相同的高度,否则头部(即标签顶部)会遮挡住标签的顶部。我们还设置了一些滚动条,以确保可以上下滚动并手动测试组件。heightfixedmainmainmin-height

目前,我们创建了一个固定的页眉,它会随着向下滚动而保持不动。不算太好,也不算太差。以下是我们代码沙箱中的初始版本:

计划:固定还是取消固定

为了隐藏或显示标题栏header,我们将使用条件类来定位它,以便轻松控制其 CSS。一个类通过将属性设置为 `true` 来固定标题栏,另一个类通过将属性设置为` false`来取消固定标题栏,从而将其隐藏起来(基于其自身 80px 的高度)。top0top-80px

我们不妨在处理 CSS 时添加一个过渡效果,这样任何变化都会在 0.3 秒内完成,而不是瞬间发生,显得突兀,说实话,根本无法使用。我郑重建议添加以下这段 CSS 代码:header

 header {
    /* ... existing properties */
    transition: all 0.3s linear;
  }
 .pin {
    top: 0;
  }
  .unpin {
    top: -80px;
  }

我们将负责根据用户滚动页面的操作来添加或移除相应的类。祝大家好运!

使用 Svelte 状态

让我们创建一个状态来保存一个值,headerClass以便在 HTML 中引用它。其实,在 Svelte 中,状态就是一个简单的 JavaScript 赋值!让我们给头部添加一个起始类名pin

<script>
  let headerClass = 'pin';
</script>

<header class={headerClass} />

真棒!像这样简单的重新赋值headerClass = "whatever"就能更新我们的视图。我们马上就去做。但首先,让我们理清思路,全面了解一下我们目前的组件:

<script>
  let headerClass = 'pin';
</script>

<style>
 header {
    background-color: darkgrey;
    height: 80px;
    position: fixed;
    width: 100%;
    transition: all 0.3s linear;
  }
  main {
    height: 150vh;
    padding-top: 80px;
  }
 .pin {
    top: 0;
  }
  .unpin {
    top: -80px;
  }
</style>

<header class={headerClass} />
<main>Lorem ipsum</main>

我们的代码正在逐步成型,但视觉效果却一成不变:仍然是一个乏味的固定头部。显然,我们必须对用户的滚动操作做出某种反应(并最终进行更新headerClass)!

滚动检测

我们首先如何检测垂直滚动?

嗯……这里一个滚动事件监听器window,我们可以随时从中读取垂直滚动位置window.scrollY。所以我们可以这样连接:

// meh
window.addEventListener('scroll', function() {
  scroll_position = window.scrollY;
  // figure out class name
}

我们需要在组件挂载时执行此操作,并记得在组件销毁时移除监听器。当然,这是一种可行的方法。

然而,在 Svelte 中我们可以减少输入:我们可以使用<svelte:window>元素,甚至可以绑定到window.scrollY位置,以便在元素位置发生变化时也能获取到它。代码如下所示:

<script>
   let y;
</script>

<svelte:window bind:scrollY={y}/>

<span>{ y }</span>

上面的代码是一个有效的组件。`in` 的值yspan随着页面上下滚动而改变(可以在沙盒环境中尝试)。此外,使用时我们无需担心移除监听器svelte:window,也无需担心检查它是否window存在(代码是否在服务器端运行)。哇,这真是太棒了!

响应式声明

这样我们就得到了滚动位置y随时间的变化数据。从这些数据流中,我们可以推导出类名。但是,每次滚动位置y发生变化时,我们该如何存储新的值呢?Svelte 提供了响应式声明语法$:。请看以下示例:

<script>
  let count = 1;
  $: double = count * 2;
  count = 2;
</script>

<span>
  { double }
</span>

span一旦我们重新赋值count给,该值将保持为 4 2

在我们的例子中,我们希望headerClass对位置做出反应y。我们将把逻辑移到一个单独的函数中,就像这样:

<script>
   let y = 0;
   let headerClass = 'pin'
   function changeClass(y) {
      // do stuff
   }
   $: headerClass = changeClass(y);
</script>

简而言之,我们可以在滚动位置改变时更新class视图。看来我们离目标越来越近了!headery

课程名称是什么?

所以我们必须专注于这个新引入的changeClass函数,它实际上是实现的最后一部分。它应该返回一个字符串,'"pin"" 或 '"unpin"",然后我们的 CSS 就可以开始生效了(实际上是滑动生效)。

基本情况

如果滚动方向没有改变,例如用户一直在向下滚动,那么我们只需要返回原来的类名即可。让我们把这种情况设为默认情况:

   let headerClass = 'pin';
   function changeClass(y) {
      let result = headerClass;
      // todo: change result as needed
      return result;
   }

这样,基本情况就处理好了。但是,如果用户开始向上滚动,函数应该返回“pin” ,如果用户开始向下滚动,函数应该返回“unpin”。我们现在有点操之过急了,因为我们甚至不知道用户是在朝哪个方向滚动;我们只有y位置信息流,所以我们先来解决这个问题。

滚动方向

我们需要将上次滚动的y位置与当前位置进行比较,才能知道滚动了多少像素。因此,我们需要lastY在每次滚动周期结束时存储一些数据,以便下一次滚动事件可以使用这些数据。

   let headerClass = 'pin';
   let lastY = 0;

   function changeClass(y) {
      let result = headerClass;
      // do stuff, then
      // just before returning the result:
      lastY = y; 
      return result;
   }

现在我们有了lastY可以操作的对象,让我们用它来获取滚动方向。如果lastY - y值为正,则用户向下滚动;否则,用户向上滚动。

   let headerClass = 'pin';
   let y = 0;
   let lastY = 0;

   function changeClass(y) {
      let result = headerClass;
      // new:
      const scrolledPxs = lastY - y;
      const scrollDirection = scrolledPxs < 0 ? "down" : "up"
      // todo: did the direction change?
      lastY = y;
      return result;
   }

为了判断滚动方向是否改变,我们可以将其与上次滚动方向进行比较,就像我们lastY之前对 `infect` 所做的那样。我们将它初始化为 `true`,"up"以便在初始向下滚动时触发我们的效果(隐藏标题栏)。

   let headerClass = 'pin';
   let y = 0;
   let lastY = 0;
   let lastDirection = 'up'; // new

   function changeClass(y) {
      let result = headerClass
      const scrollPxs = lastY - y;
      const scrollDirection = scrolledPxs < 0 ? "down" : "up"
      // new:
      const changedDirection = scrollDirection !== lastDirection;
      // todo: change result if the direction has changed
      lastDirection = scrollDirection;
      lastY = y;
      return result;
   }

合适的班级

如果我的计算正确,那么只剩最后一步了:重新分配result滚动方向何时真正改变,而我们现在已经知道这一点了。

   let headerClass = 'pin';
   let y = 0;
   let lastY = 0;
   let lastDirection = 'up';

   function changeClass(y) {
      let result = headerClass
      const scrollPxs = lastY - y;
      const scrollDirection = scrolledPxs < 0 ? "down" : "up"
      const changedDirection = scrollDirection !== lastDirection;
      if(changedDirection) { // new
        result = scrollDirection === 'down' ? 'pin' : 'unpin';
        lastDirection = scrollDirection;
      }
      lastY = y
      return result;
   }

这招果然奏效!多亏了我们的条件类header和 CSS,我们才能拥有一个带有头部空间的头部样式!

全部

咱们来看看整个 Svelte 组件吧?我们来用CSS 变量,这样就不用80px在很多地方硬编码头部高度了。

<script>
  let headerClass = "pin";
  let y = 0;
  let lastY = 0;
  let lastDirection = "up";

  function changeClass(y) {
    let result = headerClass;
    const scrolledPxs = lastY - y;
    const scrollDirection = scrolledPxs < 0 ? "down" : "up";
    const changedDirection = scrollDirection !== lastDirection;
    if (changedDirection) {
      result = scrollDirection === "down" ? "unpin" : "pin";
      lastDirection = scrollDirection;
    }
    lastY = y;
    return result;
  }

  $: headerClass = changeClass(y);
</script>

<svelte:window bind:scrollY={y}/>

<style>
  :root {
    --header-height: 80px;
  }
  header {
    background-color: darkgrey;
    height: var(--header-height);
    position: fixed;
    width: 100%;
    transition: all 0.3s linear;
  }
  main {
    height: 150vh;
    padding-top: var(--header-height);
  }
  .pin {
    top: 0;
  }
  .unpin {
    top: calc(var(--header-height) * -1);
  }
</style>

<header class={headerClass} />
<main>Lorem ipsum</main>


这里有一个包含这段代码的沙箱供您体验:

感谢阅读,祝您编程愉快!欢迎留言或在推特上与我联系。

文章来源:https://dev.to/collardeau/building-a-headroom-style-header-in-svelte-e12