B站首页的 Banner 这么好看,我用原生 JS + 三大框架统统给你复刻一遍!

写在前面

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

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

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

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

gitee.com/CrimsonHu/b...

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

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

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

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

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

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

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

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

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

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

使用原生代码实现

1. 资源获取

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

2. 定义基准移动量

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

js 复制代码
{ 
    type: 'image', 
    file: '[email protected]', 
    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 
},

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

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

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

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

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

js 复制代码
[
    { type: 'image', file: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', width: 1920, height: 180, x: 0, y: 0, r: 0, s: 1, o: 1, newX: -43.5455, newY: 0, newRotate: 0, bench: -1.17573 },
]

3. 展示静态效果

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

html 复制代码
<!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>

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

4. 实现完整功能

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

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

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

gitee.com/CrimsonHu/b...

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

html 复制代码
<!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;
                } else if (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;
            } else if (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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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>
相关推荐
ZSK62 分钟前
【HTML】分享一个自己写的3*3拼图小游戏
前端·javascript·html
Yvette-W1 小时前
【JavaScript】原型链 prototype 和 this 关键字的练习(老虎机)
开发语言·前端·javascript·ecmascript·原型模式
程序员JerrySUN1 小时前
设计模式 Day 4:观察者模式(Observer Pattern)深度解析
javascript·观察者模式·设计模式
烂蜻蜓1 小时前
HTML5 新元素:革新网页开发体验
前端·html·html5
CsharpDev-奶豆哥2 小时前
使用JS+HTML+CSS编写提词器实例
javascript·css·html
在下千玦2 小时前
#关于require 与 import 相关了解
javascript·node.js
森叶2 小时前
利用本地 Express Web 服务解决复杂的 Electron 通信链路的问题
前端·electron·express
土豆丶杨2 小时前
vue3+electron 桌面应用初始化
javascript·vue.js·electron
冴羽3 小时前
SvelteKit 最新中文文档教程(18)—— 浅层路由和 Packaging
前端·javascript·svelte
哀木3 小时前
聊聊前端埋点 clarity:我会一直视监你... 永远...
前端