$$demo <div class="carousel"> <div class="container"></div> <div class="shift"> <div class="btn left"><</div> <div class="btn right">></div> </div> <div class="bottom"></div> </div>

<script> const model = { images: [ "https://3yya.com/examples/assets/诸葛亮.jpg", "https://3yya.com/examples/assets/进击.jpg", "https://3yya.com/examples/assets/进击-大.jpg", ], // 循环播放 timerID: null, // 当前显示的图片序号 _index: 0, get imageAmount() { // 图片的数量 return this.images.length }, set index(value) { if (value < 0) { this._index = this.imageAmount - 1 } else if (value >= this.imageAmount) { this._index = 0 } else { this._index = value }

        view.render()
    },
    get index() {
        return this._index
    },
}

function resetWrapper(func) {
    // 装饰器,每次重置自动滚动
    return function (...args) {
        if (model.timerID) {
            clearInterval(model.timerID)
        }
        model.timerID = controller.run()

        return func(...args)
    }
}
const controller = {
    init() {
        // 自动滚动
        model.timerID = this.run()

        document.querySelector(".carousel .left").onclick = this.leftShift
        document.querySelector(".carousel .right").onclick = this.rightShift
    },
    leftShift: resetWrapper(() => {
        model.index -= 1
    }),
    rightShift: resetWrapper(() => {
        model.index += 1
    }),
    setIndex: resetWrapper((idx) => {
        model.index = idx
    }),
    run() {
        return setInterval(() => {
            model.index++
        }, 3000)
    },
}

const view = {
    init() {
        // 添加图片
        const container = document.querySelector(".carousel .container")
        for (let url of model.images) {
            const image = document.createElement("img")
            image.src = url

            container.append(image)
        }

        this.render()
    },
    render: function () {
        carousel = document.querySelector(".carousel")

        carousel.querySelector(".container").style.left = `${
            model.index * carousel.clientWidth * -1
        }px`

        const bottom = carousel.querySelector(".bottom")

        // 重置底部按钮
        bottom.innerHTML = ""
        for (let i = 0; i < model.imageAmount; i++) {
            // 创建底部指示器
            const indicator = document.createElement("div")
            indicator.classList.add("indicator")

            if (i === model.index) {
                // 当前图片指示器
                indicator.classList.add("activate")
            }

            indicator.onclick = () => controller.setIndex(i)

            bottom.append(indicator)
        }
    },
}

controller.init()
view.init()

</script>

<style> .carousel { width: 800px; height: 400px;

    margin: 0 auto;

    overflow: hidden;

    position: relative;
}

.carousel .container {
    width: 100%;
    height: 100%;

    display: flex;

    position: relative;
    left: 0;

    transition: left 1s;
}

.carousel .container img {
    width: 100%;
    height: 100%;

    object-fit: cover;

    flex-shrink: 0;
}

.carousel .shift .btn {
    background-color: teal;
    color: white;

    font-size: 40px;
    font-weight: bold;

    width: 50px;
    height: 50px;

    line-height: 50px;
    text-align: center;

    border-radius: 8px;
    opacity: 0.5;

    cursor: pointer;

    position: absolute;

    /* 垂直居中 */
    top: 0;
    bottom: 0;
    margin: auto;
}

.carousel .shift .left {
    left: 30px;
}

.carousel .shift .right {
    right: 30px;
}

.carousel:hover .btn {
    /* 悬浮时显示按钮 */
    opacity: 1;
}

.carousel .bottom {
    position: absolute;

    bottom: 20px;
    left: 0;
    right: 0;

    margin: auto;
    width: max-content;

    display: flex;
    gap: 10px;
}

.carousel .bottom .indicator {
    height: 5px;
    width: 40px;
    background-color: teal;
    opacity: 0.5;

    cursor: pointer;
}
.carousel:hover .bottom .indicator {
    /* 悬浮时显示按钮 */
    opacity: 1;
}

.carousel .bottom .indicator.activate {
    background-color: pink;
}

</style>

$$

$$answer

<div class="carousel">
    <div class="container"></div>
    <div class="shift">
        <div class="btn left">&lt;</div>
        <div class="btn right">&gt;</div>
    </div>
    <div class="bottom"></div>
</div>

<script>
    const model = {
        images: [
            "https://3yya.com/examples/assets/诸葛亮.jpg",
            "https://3yya.com/examples/assets/进击.jpg",
            "https://3yya.com/examples/assets/进击-大.jpg",
        ],
        // 循环播放
        timerID: null,
        // 当前显示的图片序号
        _index: 0,
        get imageAmount() {
            // 图片的数量
            return this.images.length
        },
        set index(value) {
            if (value < 0) {
                this._index = this.imageAmount - 1
            } else if (value >= this.imageAmount) {
                this._index = 0
            } else {
                this._index = value
            }

            view.render()
        },
        get index() {
            return this._index
        },
    }

    function resetWrapper(func) {
        // 装饰器,每次重置自动滚动
        return function (...args) {
            if (model.timerID) {
                clearInterval(model.timerID)
            }
            model.timerID = controller.run()

            return func(...args)
        }
    }
    const controller = {
        init() {
            // 自动滚动
            model.timerID = this.run()

            document.querySelector(".carousel .left").onclick = this.leftShift
            document.querySelector(".carousel .right").onclick = this.rightShift
        },
        leftShift: resetWrapper(() => {
            model.index -= 1
        }),
        rightShift: resetWrapper(() => {
            model.index += 1
        }),
        setIndex: resetWrapper((idx) => {
            model.index = idx
        }),
        run() {
            return setInterval(() => {
                model.index++
            }, 3000)
        },
    }

    const view = {
        init() {
            // 添加图片
            const container = document.querySelector(".carousel .container")
            for (let url of model.images) {
                const image = document.createElement("img")
                image.src = url

                container.append(image)
            }

            this.render()
        },
        render: function () {
            carousel = document.querySelector(".carousel")

            carousel.querySelector(".container").style.left = `${
                model.index * carousel.clientWidth * -1
            }px`

            const bottom = carousel.querySelector(".bottom")

            // 重置底部按钮
            bottom.innerHTML = ""
            for (let i = 0; i < model.imageAmount; i++) {
                // 创建底部指示器
                const indicator = document.createElement("div")
                indicator.classList.add("indicator")

                if (i === model.index) {
                    // 当前图片指示器
                    indicator.classList.add("activate")
                }

                indicator.onclick = () => controller.setIndex(i)

                bottom.append(indicator)
            }
        },
    }

    controller.init()
    view.init()
</script>

<style>
    .carousel {
        width: 800px;
        height: 400px;

        margin: 0 auto;

        overflow: hidden;

        position: relative;
    }

    .carousel .container {
        width: 100%;
        height: 100%;

        display: flex;

        position: relative;
        left: 0;

        transition: left 1s;
    }

    .carousel .container img {
        width: 100%;
        height: 100%;

        object-fit: cover;

        flex-shrink: 0;
    }

    .carousel .shift .btn {
        background-color: teal;
        color: white;

        font-size: 40px;
        font-weight: bold;

        width: 50px;
        height: 50px;

        line-height: 50px;
        text-align: center;

        border-radius: 8px;
        opacity: 0.5;

        cursor: pointer;

        position: absolute;

        /* 垂直居中 */
        top: 0;
        bottom: 0;
        margin: auto;
    }

    .carousel .shift .left {
        left: 30px;
    }

    .carousel .shift .right {
        right: 30px;
    }

    .carousel:hover .btn {
        /* 悬浮时显示按钮 */
        opacity: 1;
    }

    .carousel .bottom {
        position: absolute;

        bottom: 20px;
        left: 0;
        right: 0;

        margin: auto;
        width: max-content;

        display: flex;
        gap: 10px;
    }

    .carousel .bottom .indicator {
        height: 5px;
        width: 40px;
        background-color: teal;
        opacity: 0.5;

        cursor: pointer;
    }
    .carousel:hover .bottom .indicator {
        /* 悬浮时显示按钮 */
        opacity: 1;
    }

    .carousel .bottom .indicator.activate {
        background-color: pink;
    }
</style>

$$