You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

关键修改点

  1. 移除无效的scrollY绑定和元素滚动监听,改用window的wheel事件获取滚动增量
  2. 新增MAX_SCROLL变量限制滚动进度最大值,避免过度滚动
  3. 调整.wrapper和body样式,确保页面不实际滚动,保持元素居中
  4. 给遮罩添加transition让透明度变化更平滑
  5. 修正.intro-foreground高度为100vh,避免超出视口

内容的提问来源于stack exchange,提问作者Redmac

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 17:43:23