在 Svelte 中构建一个带顶棚的过梁
布局
计划:固定还是取消固定
使用 Svelte 状态
滚动检测
响应式声明
课程名称是什么?
全部
让我们用Svelte构建一个带有头部空间的页眉!本博客文章的目标是创建一个页眉,当用户向下滚动页面时,页眉会向上滑动(并移出屏幕),而当用户向上滚动页面时,页眉会重新出现(无论用户滚动到页面的哪个位置)。
这是一种节省屏幕空间的技术,同时还能避免用户必须向上滚动页面才能看到页眉和导航栏。
我们不会使用流行的headroom.js,而是自己开发一个简单的解决方案,同时在这个过程中磨练我们的 Svelte 技能。准备好了吗?
布局
我们将从一个带有fixed头部信息的组件开始,就像它已经被“固定”了一样。让我们给头部信息添加一个 `<header>` 标签height,background-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` 的值y会span随着页面上下滚动而改变(可以在沙盒环境中尝试)。此外,使用时我们无需担心移除监听器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