Svelte中如何实现滚动时固定元素的淡入淡出效果?
问题
我希望实现用户滚动时背景图渐变为黑色的效果,但要求图片在淡出过程中始终保持在屏幕中央。
这带来了一个问题:由于元素和窗口在淡出过程中都不会移动,无法使用window.scrollY或e.target.scrollTop来获取滚动信息。
本效果参考某网站开场,需要通过滚动操作控制淡入淡出(而非单纯计时动画),将滚动信息传递给黑色遮罩的透明度。
已尝试方案
- 最初尝试使用
scrollY或scrollTop,但这两个值始终为0 - 随后尝试设置与滚动速度匹配的动态
margin-top,仍无法控制淡入淡出效果
失败代码
<script lang="ts"> const INTRO_SCROLL_LENGTH = 1500; const FADE_SCROLL_LENGTH = 800; let y: number; function clamp(input: number, min: number, max: number) { return Math.min(Math.max(input, min), max); } const elementScrollY = (e) => { if (!e.target.scrollTop) { return; } y = e.target.scrollTop; console.log(e.target.scrollTop); }; </script> <svelte:window bind:scrollY={y} /> <section class="wrapper scroll-lock" on:scroll|preventDefault={elementScrollY} style="margin-top: {y}px;" > <div class="debug-stats">{y}</div> <div class="intro-darkener" style="opacity: {100 * clamp((y - INTRO_SCROLL_LENGTH) / FADE_SCROLL_LENGTH, 0, 1)}%;" /> <div class="intro-foreground" style="transform: scale3d( {clamp((0.7 * y) / INTRO_SCROLL_LENGTH + 1, 1, 1.7)}, {clamp((0.7 * y) / INTRO_SCROLL_LENGTH + 1, 1, 1.7)}, 1);" /> <div class="intro-background" style="transform: scale3d( {clamp((0.3 * y) / INTRO_SCROLL_LENGTH + 1, 1, 1.3)}, {clamp((0.3 * y) / INTRO_SCROLL_LENGTH + 1, 1, 1.3)}, 1);" /> </section> <style lang="postcss"> .wrapper { position: relative; } .debug-stats { position: absolute; top: 10px; left: 10px; z-index: 5; } .intro-background { width: 100%; height: 100vh; background-image: url('background.png'); background-position: 50% 50%; background-size: cover; background-repeat: no-repeat; will-change: transform; transform-style: preserve-3d; } .intro-foreground { position: absolute; top: 0px; z-index: 1; overflow: visible; width: 100%; height: 110vh; background-image: url('foreground.png'); background-position: 50% 50%; background-size: cover; background-repeat: no-repeat; will-change: transform; transform-style: preserve-3d; } .intro-darkener { position: absolute; z-index: 3; width: 100%; height: 100vh; background-color: #000; } .scroll-lock { overflow-y: visible; overflow-x: hidden; } </style>
解决方案
核心思路是监听**滚轮事件(wheel)**获取滚动增量,累计增量作为模拟的滚动进度,以此控制遮罩透明度和图片缩放。
修改后的代码如下:
<script lang="ts"> const INTRO_SCROLL_LENGTH = 1500; const FADE_SCROLL_LENGTH = 800; const MAX_SCROLL = INTRO_SCROLL_LENGTH + FADE_SCROLL_LENGTH; let scrollProgress = 0; function clamp(input: number, min: number, max: number) { return Math.min(Math.max(input, min), max); } // 监听滚轮事件,累计滚动进度 const handleWheel = (e: WheelEvent) => { // 向下滚动增加进度,向上滚动减少进度 scrollProgress = clamp(scrollProgress - e.deltaY, 0, MAX_SCROLL); }; </script> <svelte:window on:wheel={handleWheel} /> <section class="wrapper"> <div class="debug-stats">{scrollProgress}</div> <div class="intro-darkener" style="opacity: {100 * clamp((scrollProgress - INTRO_SCROLL_LENGTH) / FADE_SCROLL_LENGTH, 0, 1)}%;" /> <div class="intro-foreground" style="transform: scale3d( {clamp((0.7 * scrollProgress) / INTRO_SCROLL_LENGTH + 1, 1, 1.7)}, {clamp((0.7 * scrollProgress) / INTRO_SCROLL_LENGTH + 1, 1, 1.7)}, 1);" /> <div class="intro-background" style="transform: scale3d( {clamp((0.3 * scrollProgress) / INTRO_SCROLL_LENGTH + 1, 1, 1.3)}, {clamp((0.3 * scrollProgress) / INTRO_SCROLL_LENGTH + 1, 1, 1.3)}, 1);" /> </section> <style lang="postcss"> .wrapper { position: relative; overflow: hidden; height: 100vh; } .debug-stats { position: absolute; top: 10px; left: 10px; z-index: 5; color: white; } .intro-background { width: 100%; height: 100vh; background-image: url('background.png'); background-position: 50% 50%; background-size: cover; background-repeat: no-repeat; will-change: transform; } .intro-foreground { position: absolute; top: 0; z-index: 1; width: 100%; height: 100vh; background-image: url('foreground.png'); background-position: 50% 50%; background-size: cover; background-repeat: no-repeat; will-change: transform; } .intro-darkener { position: absolute; z-index: 3; width: 100%; height: 100vh; background-color: #000; transition: opacity 0.1s ease; } body { margin: 0; overflow: hidden; } </style>
关键修改点
- 移除无效的
scrollY绑定和元素滚动监听,改用window的wheel事件获取滚动增量 - 新增
MAX_SCROLL变量限制滚动进度最大值,避免过度滚动 - 调整
.wrapper和body样式,确保页面不实际滚动,保持元素居中 - 给遮罩添加
transition让透明度变化更平滑 - 修正
.intro-foreground高度为100vh,避免超出视口
内容的提问来源于stack exchange,提问作者Redmac
相关产品推荐
相关产品推荐

