B站顶部图片跟随效果实现(附:完整代码+详细注释)

136 阅读2分钟

demo1.png B站顶部的图片跟随效果感觉挺有意思的,具体为多张图片和视频根据鼠标位置实现上下左右移动,鼠标移开后恢复原样。

好了废话不多说,直接上代码,代码里有注释,也可以复制代码到本地查看效果。

图片/视频/完整代码资源可以去仓库下载

html

  <div class="animated-banner" id="scene">
        <!-- 图片 -->
        <div class="layer"><img src="1.webp"></div>
        <div class="layer"><img src="2.webp"></div>
        <div class="layer"><img src="3.webp"></div>
        <div class="layer"><img src="4.webp"></div>
        <div class="layer"><img src="5.webp"></div>
        <div class="layer"><img src="6.webp"></div>
        <div class="layer"><img src="7.webp"></div>
        <div class="layer"><img src="8.webp"></div>
        <div class="layer"><img src="9.webp"></div>
        <div class="layer"><img src="10.webp"></div>
        <div class="layer"><img src="11.webp"></div>
        <div class="layer"><img src="12.webp"></div>
        <div class="layer"><img src="13.webp"></div>
        <!-- 视频 设置静音 自动播放 -->
        <div class="layer">
            <video src="14.webm" loop playsinline width="200" height="500" autoplay muted
                style="object-fit: cover; height: 500px; width: 200px; transform: translate(400px, 0px) rotate(0deg) scale(1); opacity: 1;">
            </video>
        </div>
        <div class="layer"><img src="15.webp"></div>
        <div class="layer"><img src="16.webp"></div>
        <div class="layer"><img src="17.webp"></div>
        <div class="layer"><img src="18.webp"></div>
        <div class="layer"><img src="19.webp"></div>
        <!-- 视频 设置静音 自动播放 -->
        <div class="layer">
            <video src="20.webm" loop playsinline width="200" height="500" autoplay muted
                style="object-fit: cover; height: 500px; width: 200px; transform: translate(170px, 0px) rotate(0deg) scale(1); opacity: 1;">
            </video>
        </div>
        <!-- 视频 设置静音 自动播放 -->
        <div class="layer">
            <video src="21.webm" loop playsinline width="200" height="500" autoplay muted
                style="object-fit: cover; height: 500px; width: 200px; transform: translate(-600px, 0px) rotate(0deg) scale(1); opacity: 1;">
            </video>
        </div>


    </div>

css

    * {
        margin: 0;
        padding: 0;
    }

    body {
        box-sizing: border-box;
    }

    .animated-banner {
        display: flex;
        justify-content: center;
        margin: 0 auto;
        min-width: 1000px;
        min-height: 155px;
        height: 9.375vw;
        max-height: 240px;

        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        right: 0;
        overflow: hidden;

    }

    .layer {
        position: absolute;
        left: -150px;
        top: 0;
        height: 100%;
        /* 宽度比视口宽300像素  防止左右滑动看见图片栅栏中断的情况*/
        width: calc(100vw + 300px);
        display: flex;
        align-items: center;
        justify-content: center;
        overflow: hidden;
        /* 添加平滑过渡效果 */
        transition: transform 0.1s ease;
    }

    img {
        width: 100%;
        height: 100%;
    }

js

  document.addEventListener('DOMContentLoaded', () => {
        const banner = document.querySelector('.animated-banner');
        const layers = document.querySelectorAll('.layer');

        // 数组的长度 == 元素个数
        const movementFactor = Array.from({ length: layers.length }, (_, i) => (i + 1) * 0.3);


        let isMouseInside = false; // 用于标记鼠标是否在banner内
        let lastMouseX = 0; // 上一次鼠标X坐标

        const handleMouseMove = (event) => {
            if (!isMouseInside) return; // 鼠标尚未进入区域时不做处理

            const mouseX = event.clientX - banner.getBoundingClientRect().left; // 计算鼠标相对banner的X位置
            const bannerWidth = banner.offsetWidth;

            // 计算相对移动量
            const deltaX = mouseX - lastMouseX;
            // console.log(deltaX);

            // 只有在鼠标位置发生变化时才进行位置调整
            if (Math.abs(deltaX) > 1) {
                layers.forEach((layer, index) => {
                    const movementX = deltaX * movementFactor[index] / 50;

                    if (index == 16) {//这个水滴是斜向移动的 水滴移动的范围好像比背景和人物小点 所以除2
                        layer.style.transform = `translate(${movementX / 2}px,${movementX / 2}px)`;
                    } else if (index == 9 || index == 10) {//这个水滴是上下移动的
                        layer.style.transform = `translateY(${movementX / 2}px)`;
                    } else if (index == 4) {//这张小鲸鱼图片是反着移动的把movementX值改为负的
                        layer.style.transform = `translateX(${-movementX}px)`;
                    } else {
                        layer.style.transform = `translateX(${movementX}px)`;
                    }
                });
            }

        }

        const handleMouseEnter = (event) => {
            isMouseInside = true; // 鼠标进入时标记为true
            lastMouseX = event.clientX - banner.getBoundingClientRect().left; // 初始化上一次的鼠标位置为当前进入位置
            layers.forEach(layer => {
                layer.style.transition = 'none'; // 鼠标进入时禁用过渡效果
            });
        };

        const handleMouseLeave = () => {
            isMouseInside = false; // 鼠标离开时标记为false
            layers.forEach(layer => {
                layer.style.transition = 'transform 0.5s ease'; // 恢复过渡效果
                layer.style.transform = `translateX(0px)`; // 恢复到初始位置
            });

        };

        banner.addEventListener('mousemove', handleMouseMove);
        banner.addEventListener('mouseenter', handleMouseEnter);
        banner.addEventListener('mouseleave', handleMouseLeave);
    });