B站首页的 Banner 这么好看,我让你直接用到你的项目!_html

写在前面

我最开始是用 Angular 去实现了B站的 Banner ,那时候还没有人做这东西,可以看到下面那一长串 Bannet 图片的第一个图是好几年以前的了。然后随着逐步完善,在这几年偶尔也看到有人发过这东西的实现方法。

但我为什么要写这篇文章?「因为我打算用原生 JS 和三大框架都去实现一遍,以满足所有人的需求,让你能直接复制到你的项目」。而且我作为几乎每一期都 copy 的玩家,存货多,也知道最简单通用的 copy 方法。

本文的原生 JS 代码我直接从我已经完善的 Angular 代码基础上提取出来,然后再用 React 与 Vue 各自实现一遍。主要讲讲我大致的方法原理,里面的具体实现细节就不讲了,毕竟讲起来费劲,主要就是一些根据鼠标移动计算图片位置,自己认真读起来不会很难。

「代码与相关静态资源已提交至 gitee ,需要的直接去看完整代码即可。」

 https://gitee.com/CrimsonHu/bilibili-banner

这是我的个人网站,我在我的网站上面用它很久了~~~

 https://intelyes.club

B站首页的 Banner 这么好看,我让你直接用到你的项目!_bc_02

B站首页的 Banner 这么好看,我让你直接用到你的项目!_bc_03

B站是怎么实现 Banner 的

我们打开控制台,首先找到它的 DOM 结构,可以看到 Banner 里面有一个个 class 为 layer 的 div:

B站首页的 Banner 这么好看,我让你直接用到你的项目!_d3_04

点开 div,会看到一个 img 与控制这个 img 位置与大小的样式:

B站首页的 Banner 这么好看,我让你直接用到你的项目!_bc_05

Banner 的本质就是一层层的图片,按照特定的宽度、高度、位置( translate 的 X 与 Y 属性)、以及角度、缩放、透明度这些来进行排列的。

在 Banner 上移动鼠标,我们会发现 「transform 属性的各项值会随着鼠标的移动而更新」

B站首页的 Banner 这么好看,我让你直接用到你的项目!_d3_06

所以实现思路就有了:「把这些图片按照指定的样式进行排列,然后做一个鼠标移动的监听事件即可」

那么如何知道鼠标移动了一定的距离,每个图片各自的移动量是多少呢?

这个也简单。因为它的移动量基本都是线性的。目前我只看到有两期是在线性移动的基础上加了点额外的效果,这个在这里就把它忽略。把它的线性动画的移动量拿过来后已经显示的很不错了。

于是我发现了一个很好用的方法:

「以某一张图片为基准,以它的移动量为一个基准单位,去看其它每个图片移动了多少。」

例如,我以图片 A 移动了 5px 为基准,那么我就看图片 A 移动了 5px 的时候,其它的各个图片分别移动了多少。所以,通过这个取基准的方法,我们可以得出,「根据鼠标的移动,带动图片 A 的位置更新,从而得出其它各个图片的位置的更新值」

使用原生代码实现

1. 资源获取

现在理清了原理,那么我们就先将每张图片的静态资源获取下来,这一步就省略不写了。

2. 定义基准移动量

现在我们以第一张图片为基准,去定义移动量:

{ 
    type: 'image', 
    file: '477fb7a2f5e2cc1b2aa00c679c8ab168b47ff1b9.png@1c.webp', 
    width: 1728, 
    height: 162, 
    x: 0,  // 原始 translateX 值
    y: 0,  // 原始 translateY 值
    r: 0,  // 原始 rotate 值
    s: 1,  // 原始 scale 值 
    o: 1,  // 原始 opacity 值 
    newX: -1.17573, // 更新后的 translateX 值
    newY: 0,        // 更新后的 translateY 值
    newRotate: 0,   // 更新后的 rotate 值
    bench: -1.17573
},
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

其实没更新的值可以省略不写,比如这段定义里面的 newOpacity 我省略了,而且 newY 和 newRotate 也可以省略,因为后续代码我会处理省略的字段不进行计算更新。

这段定义代码需要注意的地方就是 x、 newX、 bench 这三个,这是我们定义的一个基准移动量。

「鼠标在 Banner 上移动,这个图片的 translateX 值从 0 更新到 -1.17573,以此为基准,基准字段记为 bench。」

于是,需要你花亿点点时间,去看第一个图片的 translateX 在 -1.17573 这个值的时候,其它的图片的位置值各自是多少:

B站首页的 Banner 这么好看,我让你直接用到你的项目!_bc_07

因为每次重新打开页面重新打开控制台时,这种细微操作的移动,更新的值都不会完全一致,图中的值会与我记录的稍微不一致,所以在完成所有图片的数据采集之前不要更改窗口尺寸与开关控制台。

[
    { type: 'image', file: '477fb7a2f5e2cc1b2aa00c679c8ab168b47ff1b9.png@1c.webp', width: 1728, height: 162, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -1.17573, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'dd4aa5af4898a15dde074fe6b833bdfbc045dd47.png@1c.webp', width: 1632, height: 153, x: -42.5, y: 0, r: 0, s: 1, o: 1, newX: -46.2014, newY: 1.48055, newRotate: -2.17727, bench: -1.17573 },
    { type: 'image', file: 'ae64c474ba2748b4fea1a1433b9b373598d3e686.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: 0, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'd41b0292d9d1b0fce37a35f6efacac579093cdb3.png@1c.webp', width: 1728, height: 162, x: -63, y: -18, r: 0, s: 1, o: 1, newX: -68.8786, newY: -18, newRotate: -2.17727, bench: -1.17573 },
    { type: 'image', file: 'a12cbab877db2b6d3b96ec012735cfe3998de399.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: 3.70136, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: '6f27fe80f495e83546921c2c58b2107c2ee89706.png@1c.webp', width: 1824, height: 171, x: 9.5, y: 0, r: 0, s: 1, o: 1, newX: -0.842045, newY: -2.06841, newRotate: 0, newScale: 0.978227, bench: -1.17573 },
    { type: 'image', file: 'ac580ac636595e27b5d5c8b1f9d802751823f02f.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: 14.8055, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: '987a407da2e02a12be549ad7f011dc11f73216e9.png@1c.webp', width: 1632, height: 153, x: 0, y: 8.5, r: 0, s: 1, o: 1, newX: 5.55205, newY: 7.38959, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: '843ee84b16bab6274b98b317b3adaf1351b4be76.png@1c.webp', width: 1632, height: 153, x: 8.5, y: 0, r: 0, s: 1, o: 1, newX: -2.60409, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'a5607001ac06b4780106e2a10aa835657e553f07.png@1c.webp', width: 1632, height: 153, x: 8.5, y: 0, r: 0, s: 1, o: 1, newX: 15.9027, newY: -12.9548, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'b14cad3c76e957705810c26f4c6acc236f280721.png@1c.webp', width: 1632, height: 153, x: 17, y: 0, r: 0, s: 1, o: 1, newX: 11.448, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'f569be6834a5968c038bcc0fb0403f8f77e24c18.png@1c.webp', width: 1632, height: 153, x: 8.5, y: 0, r: 0, s: 1, o: 1, newX: 1.09727, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'eb98a42561de7d45f8856407c4531d2724ac6fc6.png@1c.webp', width: 1344, height: 126, x: 21, y: 21, r: 0, s: 1, o: 1, newX: -9.48182, newY: 17.9518, newRotate: 2.17727, newScale: 1.13064, bench: -1.17573 },
    { type: 'image', file: '480a5c02dbcd3afd5f790cf621efbd1b2d39efcb.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -18.5068, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'cc1405d4d805baf9c459f67c03f095f70ec864c6.png@1c.webp', width: 1632, height: 153, x: 0, y: 17, r: 0, s: 1, o: 1, newX: -5.55205, newY: 18.8507, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: '1696ffeacb26bcce7ad50037952620ee43614a4a.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -7.40273, newY: -2.96109, newRotate: 0, newScale: 1.08709, bench: -1.17573 },
    { type: 'image', file: 'b0747b7f64e0e07e379a4bd416a0891b3e52c7d9.png@1c.webp', width: 1651.2, height: 154.8, x: 34.4, y: 0, r: 0, s: 1, o: 1, newX: 11.9305, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'ec9f99bf71be9408a359d7cd48dfb47e48ebce2c.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -9.25341, newY: 6.29232, newRotate: -8.70909, newScale: 1.13064, bench: -1.17573 },
    { type: 'image', file: 'c4cef2b063a853521c7f413d8e7782ff653e4126.png@1c.webp', width: 1632, height: 153, x: 17, y: 0, r: 0, s: 1, o: 1, newX: -12.6109, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: '567e0221b3bd89e34d85c604549d915c95ca17d1.png@1c.webp', width: 1920, height: 180, x: 30, y: 0, r: 0, s: 1, o: 1, newX: 73.5455, newY: 0, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: '23c05e4d9e0ffbc16671e14c406fbe785b06ff9f.png@1c.webp', width: 1632, height: 153, x: 15.3, y: 17, r: 0, s: 1, o: 1, newX: -18.0123, newY: 17, newRotate: 0, bench: -1.17573 },
    { type: 'image', file: 'fbfeb5de7e41fd1e888fca7ac76cbf2f8a42f3d3.png@1c.webp', width: 1920, height: 180, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -43.5455, newY: 0, newRotate: 0, bench: -1.17573 },
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

3. 展示静态效果

有了这些数据后,先来把每个图层的图片按照这些位置数据显示出来,用原生 JS 做一个静态展示:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .bili-banner {
            position: relative;
            width: 100%;
            height: 100%;
            background-color: #f9f9f9;
            display: flex;
            justify-content: center;
            background-repeat: no-repeat;
            background-position: center 0;
            background-size: cover;
            filter: brightness(var(--img-filter-brightness));
        }

        .animated-banner {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            overflow: hidden;
        }

        .layer {
            position: absolute;
            height: 100%;
            width: 100%;
            left: 0;
            top: 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .logo {
            position: absolute;
            width: 220px;
            height: 105px;
            left: 0;
            top: 0;
            transform: scale(0.4) translate(-60%, -60%);
        }

        .logo>img {
            width: 100%;
            height: 100%;
        }

        .banner-container {
            position: relative;
            width: 800px;
            height: 150px;
        }
    </style>
</head>

<body>
    <div class="banner-container" id="winter-5"></div>
</body>

<script>
    const baseSrc = './assets/winter-5/';

    const imgData = [...];

    const container = document.getElementById('winter-5');

    const biliBanner = document.createElement('div');
    biliBanner.className = 'bili-banner';

    const animatedBanner = document.createElement('div');
    animatedBanner.className = 'animated-banner';

    for (let i = 0; i < imgData.length; i++) {
        const layer = document.createElement('div');
        layer.className = 'layer';
        const img = document.createElement('img');
        img.src = baseSrc + imgData[i].file;
        img.style.width = imgData[i].width + 'px';
        img.style.height = imgData[i].height + 'px';
        img.style.transform = '' +
            'translate(' +
            imgData[i].x + 'px, ' +
            imgData[i].y + 'px' +
            ')' + ' ' +
            'rotate(' + imgData[i].r + 'deg)' + ' ' +
            'scale(' + imgData[i].s + ')';
        img.style.opacity = imgData[i].o;
        layer.appendChild(img);
        animatedBanner.appendChild(layer);
    }

    biliBanner.appendChild(animatedBanner);
    container.appendChild(biliBanner);

</script>

</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.

上述代码的效果如图所示:

B站首页的 Banner 这么好看,我让你直接用到你的项目!_d3_08

4. 实现完整功能

现在能够看到每张图片能够按照正确的位置显示出来了,接下来的主要工作就是「添加鼠标移动事件,去计算每个图片的移动量」。具体就不详细讲了。

B站首页的 Banner 这么好看,我让你直接用到你的项目!_d3_09

可以看到,已经基本达到了B站首页 Banner 的效果。

「使用三大框架各自实现的代码直接在 gitee 上看,文中就不贴出来了。」

 https://gitee.com/CrimsonHu/bilibili-banner

下面是原生 JS 实现的完整代码,贴在这里供大家学习参考。「这里我使用了面向对象的方式将其进行封装,方便初始化多个实例与销毁」。同时我也建议前端人应该同时掌握面向对象与函数式,「在合适的地方用合适的方法」,而不是一味地吹捧函数式编程,去拒绝面向对象的思维。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .bili-banner {
            position: relative;
            width: 100%;
            height: 100%;
            background-color: #f9f9f9;
            display: flex;
            justify-content: center;
            background-repeat: no-repeat;
            background-position: center 0;
            background-size: cover;
            filter: brightness(var(--img-filter-brightness));
        }

        .animated-banner {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            overflow: hidden;
        }

        .layer {
            position: absolute;
            height: 100%;
            width: 100%;
            left: 0;
            top: 0;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .logo {
            position: absolute;
            width: 220px;
            height: 105px;
            left: 0;
            top: 0;
            transform: scale(0.4) translate(-60%, -60%);
        }

        .logo>img {
            width: 100%;
            height: 100%;
        }

        .banner-container {
            position: relative;
            width: 800px;
            height: 150px;
        }
    </style>
</head>

<body>
    <div class="banner-container" id="winter-5"></div>
</body>

<script>

    class ResizeObserverWrap {
        observer;
        dom;
        constructor(dom, callback) {
            this.dom = dom;
            this.observer = new ResizeObserver((entries) => {
                if (!Array.isArray(entries) || !entries.length) {
                    return;
                }
                for (let entry of entries) {
                    callback(entry.target);
                }
            });
            this.observer.observe(this.dom);
        }
        unmount() {
            this.observer.unobserve(this.dom);
        }
    }

    class BilibiliBannerBase {

        baseSrc = './assets/winter-5/';

        imgDomList = [];
        imgData = [];
        imgNewData = [];

        resizeObserver;
        container;
        containerWidth = 0;

        // 声明自定义参数
        marginLeft = 0;
        moveRate = 300;
        maxMove = null;
        maxLeftPosition = null;
        maxRightPosition = null;

        // 声明状态信息
        isReseting = false;
        isInResetingEnter = false;
        startPoint = { x: 0, y: 0 }
        transition = 0.75;
        moveX = 0;

        // 声明loop与render方法更新界面
        isDestroyed = false;
        frameTime = 0;
        fps = 60;

        // 监听事件
        mouseenter = (e) => {
            if (this.isReseting) {
                this.isInResetingEnter = true;
            } else {
                this.bilibiliStart(e.clientX, e.clientY);
            }
        }
        mousemove = (e) => {
            if (!this.isReseting) {
                if (this.isInResetingEnter) {
                    this.bilibiliStart(e.clientX, e.clientY);
                    this.isInResetingEnter = false;
                } else {
                    this.bilibiliMove(e.clientX, e.clientY);
                }
            }
        }
        mouseleave = (e) => {
            this.bilibiliEnd();
            this.reset();
        }

        constructor(param = {
            container,
            imgData,
            marginLeft,
            moveRate,
            maxMove: { left, right },
            maxLeftPosition: { index, cut },
            maxRightPosition: { index, cut },
        }) {
            window.requestAnimationFrameFunc = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
            this.container = param.container;
            this.imgData = param.imgData;
            this.init();
        }

        init() {
            const biliBanner = document.createElement('div');
            biliBanner.className = 'bili-banner';
            const animatedBanner = document.createElement('div');
            animatedBanner.className = 'animated-banner';
            for (let i = 0; i < this.imgData.length; i++) {
                const layer = document.createElement('div');
                layer.className = 'layer';
                const img = document.createElement('img');
                img.src = this.baseSrc + this.imgData[i].file;
                img.style.width = this.imgData[i].width + 'px';
                img.style.height = this.imgData[i].height + 'px';
                img.style.transform = '' +
                    'translate(' +
                    this.imgData[i].x + 'px, ' +
                    this.imgData[i].y + 'px' +
                    ')' + ' ' +
                    'rotate(' + this.imgData[i].r + 'deg)' + ' ' +
                    'scale(' + this.imgData[i].s + ')';
                img.style.opacity = this.imgData[i].o;
                img.style.filter = this.imgData[i].filter == undefined ? 'none' : this.imgData[i].filter;
                this.imgDomList.push(img);
                layer.appendChild(img);
                animatedBanner.appendChild(layer);
            }
            biliBanner.appendChild(animatedBanner);
            this.container.appendChild(biliBanner);
            this.resizeObserver = new ResizeObserverWrap(this.container, (e) => {
                this.containerWidth = e.clientWidth;
            });
            this.imgData.forEach((each, i) => {
                each.x += this.marginLeft;
                each.newX += this.marginLeft;
                this.imgNewData.push({
                    currentX: each.x,
                    currentY: each.y == undefined ? 0 : each.y,
                    currentRotate: each.r == undefined ? 0 : each.r,
                    currentScale: each.s == undefined ? 0 : each.s,
                    currentOpacity: each.o == undefined ? 1 : each.o,
                });
            });
            // 添加鼠标移动事件
            this.container.addEventListener('mouseenter', this.mouseenter);
            this.container.addEventListener('mousemove', this.mousemove);
            this.container.addEventListener('mouseleave', this.mouseleave);
            this.loop(0);
        }

        destroy() {
            this.isDestroyed = true;
            this.resizeObserver.unmount();
            this.container.removeEventListener('mouseenter', this.mouseenter);
            this.container.removeEventListener('mousemove', this.mousemove);
            this.container.removeEventListener('mouseleave', this.mouseleave);
            this.container.innerHTML = '';
        }

        render() {
            this.imgDomList.forEach((img, i) => {
                img.style.transform = '' +
                    'translate(' +
                    this.imgNewData[i].currentX + 'px, ' +
                    this.imgNewData[i].currentY + 'px' +
                    ')' + ' ' +
                    'rotate(' + this.imgNewData[i].currentRotate + 'deg)' + ' ' +
                    'scale(' + this.imgNewData[i].currentScale + ')';
                img.style.opacity = this.imgNewData[i].currentOpacity;
                img.style.filter = this.imgNewData[i].filter == undefined ? 'none' : this.imgNewData[i].filter;
                img.style.transition = this.transition + 's';
            })
        }

        loop(e) {
            if (e - this.frameTime >= 1000 / this.fps) {
                this.frameTime = e;
                this.render();
            }
            window.requestAnimationFrameFunc((e1) => {
                if (!this.isDestroyed) {
                    this.loop(e1);
                }
            });
        }

        bilibiliStart(x, y) {
            this.startPoint.x = x;
            this.startPoint.y = y;
            this.transition = 0;
        }

        bilibiliMove(x, y) {
            let moveX = (x - this.startPoint.x) / this.moveRate;
            this.startPoint.x = x;
            this.moveX += moveX;
            if (this.maxMove) {
                let v = this.moveX * this.moveRate;
                if (moveX < 0 && v * -1 > this.maxMove.left) {
                    return;
                } elseif (moveX > 0 && v > this.maxMove.right) {
                    return;
                }
            }
            if (this.moveX > 0) {
                if (Math.abs(this.moveX) > this.getLeftWidth()) {
                    this.moveX -= moveX;
                    return;
                }
            } else {
                if (Math.abs(this.moveX) > this.getRightWidth()) {
                    this.moveX -= moveX;
                    return;
                }
            }
            for (let i = 0; i < this.imgData.length; i++) {
                this.moveFunction(i);
            }
        }

        bilibiliEnd() {
            this.startPoint.x = 0;
            this.startPoint.y = 0;
        }

        getLeftWidth() {
            let leftWidth;
            if (this.maxLeftPosition) {
                let i = this.maxLeftPosition.index;
                let r = (this.imgData[i].newX - this.imgData[i].x) / this.imgData[i].bench;
                leftWidth = (this.imgData[i].width - this.containerWidth) / 2 - this.imgData[i].x;
                leftWidth = leftWidth - this.maxLeftPosition.cut;
                leftWidth = leftWidth / r;
                console.log(leftWidth)
            } else {
                leftWidth = this.containerWidth / 2 - this.marginLeft;
            }
            return leftWidth;
        }

        getRightWidth() {
            let rightWidth;
            if (this.maxRightPosition) {
                let i = this.maxRightPosition.index;
                let r = (this.imgData[i].newX - this.imgData[i].x) / this.imgData[i].bench;
                rightWidth = (this.imgData[i].width - this.containerWidth) / 2 + this.imgData[i].x;
                rightWidth = rightWidth - this.maxRightPosition.cut;
                rightWidth = rightWidth / r;
            } else {
                rightWidth = this.containerWidth / 2 + this.marginLeft;
            }
            return rightWidth;
        }

        moveFunction(i) {
            let bench = this.imgData[i].bench;
            // 移动量 - X
            let x = this.imgData[i].x;
            let newX = this.imgData[i].newX;
            if (x != null && newX != null && x != newX) {
                let x1 = (newX - x) / bench;
                this.imgNewData[i].currentX = x1 * this.moveX + x;
            }
            // 移动量 - Y
            let y = this.imgData[i].y;
            let newY = this.imgData[i].newY;
            if (y != null && newY != null && y != newY) {
                let y1 = (newY - y) / bench;
                this.imgNewData[i].currentY = y1 * this.moveX + y;
            }
            // 移动量 - Rotate
            let r = this.imgData[i].r;
            let newRotate = this.imgData[i].newRotate;
            if (r != null && newRotate != null && r != newRotate) {
                let r1 = (newRotate - r) / bench;
                this.imgNewData[i].currentRotate = r1 * this.moveX + r;
            }
            // 移动量 - Scale
            let s = this.imgData[i].s;
            let newScale = this.imgData[i].newScale;
            if (s != null && newScale != null && s != newScale) {
                let s1 = (newScale - s) / bench;
                this.imgNewData[i].currentScale = s1 * this.moveX + s;
            }
            // 移动量 - Opacity
            let o = this.imgData[i].o;
            let newOpacity = this.imgData[i].newOpacity;
            if (o != null && newOpacity != null && o != newOpacity) {
                let o1 = (newOpacity - o) / bench;
                this.imgNewData[i].currentOpacity = o1 * this.moveX + o;
            }
            if (this.imgNewData[i].currentOpacity < 0) { // 透明度检测,在0~1范围内
                this.imgNewData[i].currentOpacity = 0;
            } elseif (this.imgNewData[i].currentOpacity > 1) {
                this.imgNewData[i].currentOpacity = 1;
            }
        }

        reset() {
            this.transition = 0.75;
            this.moveX = 0;
            for (let i = 0; i < this.imgData.length; i++) {
                let data = this.imgData[i];
                this.imgNewData[i].currentX = data.x;
                this.imgNewData[i].currentY = data.y == undefined ? 0 : data.y;
                this.imgNewData[i].currentRotate = data.r == undefined ? 0 : data.r;
                this.imgNewData[i].currentScale = data.s == undefined ? 0 : data.s;
                this.imgNewData[i].currentOpacity = data.o == undefined ? 0 : data.o;
            }
            this.isReseting = true;
            setTimeout(() => {
                this.isReseting = false;
            }, 750);
        }

    }

    let banner = new BilibiliBannerBase({
        container: document.getElementById('winter-5'),
        imgData: [
            { type: 'image', file: '477fb7a2f5e2cc1b2aa00c679c8ab168b47ff1b9.png@1c.webp', width: 1728, height: 162, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -1.17573, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'dd4aa5af4898a15dde074fe6b833bdfbc045dd47.png@1c.webp', width: 1632, height: 153, x: -42.5, y: 0, r: 0, s: 1, o: 1, newX: -46.2014, newY: 1.48055, newRotate: -2.17727, bench: -1.17573 },
            { type: 'image', file: 'ae64c474ba2748b4fea1a1433b9b373598d3e686.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: 0, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'd41b0292d9d1b0fce37a35f6efacac579093cdb3.png@1c.webp', width: 1728, height: 162, x: -63, y: -18, r: 0, s: 1, o: 1, newX: -68.8786, newY: -18, newRotate: -2.17727, bench: -1.17573 },
            { type: 'image', file: 'a12cbab877db2b6d3b96ec012735cfe3998de399.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: 3.70136, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: '6f27fe80f495e83546921c2c58b2107c2ee89706.png@1c.webp', width: 1824, height: 171, x: 9.5, y: 0, r: 0, s: 1, o: 1, newX: -0.842045, newY: -2.06841, newRotate: 0, newScale: 0.978227, bench: -1.17573 },
            { type: 'image', file: 'ac580ac636595e27b5d5c8b1f9d802751823f02f.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: 14.8055, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: '987a407da2e02a12be549ad7f011dc11f73216e9.png@1c.webp', width: 1632, height: 153, x: 0, y: 8.5, r: 0, s: 1, o: 1, newX: 5.55205, newY: 7.38959, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: '843ee84b16bab6274b98b317b3adaf1351b4be76.png@1c.webp', width: 1632, height: 153, x: 8.5, y: 0, r: 0, s: 1, o: 1, newX: -2.60409, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'a5607001ac06b4780106e2a10aa835657e553f07.png@1c.webp', width: 1632, height: 153, x: 8.5, y: 0, r: 0, s: 1, o: 1, newX: 15.9027, newY: -12.9548, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'b14cad3c76e957705810c26f4c6acc236f280721.png@1c.webp', width: 1632, height: 153, x: 17, y: 0, r: 0, s: 1, o: 1, newX: 11.448, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'f569be6834a5968c038bcc0fb0403f8f77e24c18.png@1c.webp', width: 1632, height: 153, x: 8.5, y: 0, r: 0, s: 1, o: 1, newX: 1.09727, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'eb98a42561de7d45f8856407c4531d2724ac6fc6.png@1c.webp', width: 1344, height: 126, x: 21, y: 21, r: 0, s: 1, o: 1, newX: -9.48182, newY: 17.9518, newRotate: 2.17727, newScale: 1.13064, bench: -1.17573 },
            { type: 'image', file: '480a5c02dbcd3afd5f790cf621efbd1b2d39efcb.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -18.5068, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'cc1405d4d805baf9c459f67c03f095f70ec864c6.png@1c.webp', width: 1632, height: 153, x: 0, y: 17, r: 0, s: 1, o: 1, newX: -5.55205, newY: 18.8507, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: '1696ffeacb26bcce7ad50037952620ee43614a4a.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -7.40273, newY: -2.96109, newRotate: 0, newScale: 1.08709, bench: -1.17573 },
            { type: 'image', file: 'b0747b7f64e0e07e379a4bd416a0891b3e52c7d9.png@1c.webp', width: 1651.2, height: 154.8, x: 34.4, y: 0, r: 0, s: 1, o: 1, newX: 11.9305, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'ec9f99bf71be9408a359d7cd48dfb47e48ebce2c.png@1c.webp', width: 1632, height: 153, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -9.25341, newY: 6.29232, newRotate: -8.70909, newScale: 1.13064, bench: -1.17573 },
            { type: 'image', file: 'c4cef2b063a853521c7f413d8e7782ff653e4126.png@1c.webp', width: 1632, height: 153, x: 17, y: 0, r: 0, s: 1, o: 1, newX: -12.6109, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: '567e0221b3bd89e34d85c604549d915c95ca17d1.png@1c.webp', width: 1920, height: 180, x: 30, y: 0, r: 0, s: 1, o: 1, newX: 73.5455, newY: 0, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: '23c05e4d9e0ffbc16671e14c406fbe785b06ff9f.png@1c.webp', width: 1632, height: 153, x: 15.3, y: 17, r: 0, s: 1, o: 1, newX: -18.0123, newY: 17, newRotate: 0, bench: -1.17573 },
            { type: 'image', file: 'fbfeb5de7e41fd1e888fca7ac76cbf2f8a42f3d3.png@1c.webp', width: 1920, height: 180, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -43.5455, newY: 0, newRotate: 0, bench: -1.17573 },
        ],
        maxMove: { left: 2000, right: 2000 },
    });
</script>

</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.