原生轮播图

16 阅读2分钟
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>carousel</title>
    <style>
        /* 轮播图 */

        .carousel {
            position: relative;
            width: 600px;
            height: 300px;
        }

        .carousel .container {
            display: flex;
            position: relative;
            left: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        .carousel .container .img-box {
            width: 100%;
            height: 100%;
            position: relative;
            left: 0;
            display: flex;
            transition: left 0.5s ease-in-out;
        }

        .carousel .container .img-box img {
            /* shrink 设置为 0 防止图片缩小 保证图片宽度撑满整个容器 */
            flex-shrink: 0;
            height: 100%;
            width: 100%;
            /* 防止图片宽高比改变 */
            object-fit: cover;
        }

        /* 左右切换按钮 */

        .carousel .left-right-btn-group .btn {
            position: absolute;
            /* 让按钮垂直居中 */
            inset: 0 auto;
            width: 30px;
            height: 30px;
            /* 垂直居中的关键 -- 上下外边距自适应 */
            margin: auto 0;
            border-radius: 50%;
            border: none;
            cursor: pointer;
            background-color: rgba(255, 255, 255, 0.3);
            color: white;
            opacity: 0.5;
            transition: opacity 0.5s ease;
        }

        /* 鼠标悬浮在按钮上时让其不透明度变为 1 */
        .carousel .left-right-btn-group .btn:hover {
            opacity: 1;
        }

        .carousel .left-right-btn-group .left {
            left: 10px;
        }

        .carousel .left-right-btn-group .right {
            right: 10px;
        }

        /* 下标选择器 */
        .carousel .index-selector-btn-group {
            display: flex;
            gap: 10px;
            position: absolute;
            /* 绝对定位 水平居中的关键 */
            left: 0;
            right: 0;
            bottom: 10px;
            margin: 0 auto;
            /* 让按钮组宽度和轮播图宽度一样宽 */
            width: max-content;
        }

        .carousel .index-selector-btn-group .btn {
            width: 20px;
            height: 1px;
            border: none;
            cursor: pointer;
            background-color: gray;
        }

        .carousel .index-selector-btn-group .active {
            background-color: #fff;
        }
    </style>
</head>

<body>
    <div class="carousel">
        <!-- <div class="container"></div> -->
    </div>

    <script>
        (() => {
            class Carousel {
                constructor(options) {
                    this.imgUrlList = options.imgUrlList;
                    this.app = options.el;
                    this.container = null;
                    this.width = options.width || 600;
                    this.height = options.height || 300;
                    this.autoPlay = options.autoPlay || true;
                    this.autoPlayDuration = options.autoPlayDuration || 3000;
                    this.curIndex = 0;
                    this.oImgBox = null;
                    this.timer = null;
                    this.init();
                    this.effect = []
                    this.isTransitioning  = false
                }
                init() {
                    this.render();
                    this.bindEvents();
                   // this.autoPlay && this.start();
                }
                bindEvents() {
                    // 绑定事件
                    this.oImgBox.addEventListener('transitionend', this.handleTransitionEnd.bind(this));
                    this.app.addEventListener('mouseenter', this.stop.bind(this));
                    this.app.addEventListener('mouseleave', this.start.bind(this));
                }


                render() {
                    // 渲染轮播图结构
                    this.container = document.createElement('div');
                    this.container.className = 'container';
                    this.oImgBox = document.createElement('div');
                    this.oImgBox.className = 'img-box';
                    const imgElements = this.imgUrlList.map((imgUrl, index) => {
                        return `<img src="${imgUrl}" />`;
                    });
                    this.oImgBox.innerHTML = imgElements.join('');
                    this.container.appendChild(this.oImgBox);
                    this.app.appendChild(this.container);

                }

                next() {
                    // 切换到下一张图片

                    this.curIndex++;
                    this.goTo(this.curIndex);
                    //this.oImgBox.style.left = this.curIndex * -600 + 'px';
                }

                prev() {
                    // 切换到上一张图片
                    this.curIndex--;
                    this.goTo(this.curIndex);
                   // this.oImgBox.style.left = this.curIndex * -600 + 'px';
                }

                goTo(index) {
                    // 切换到指定下标的图片
                    this.curIndex = index;
                    this.oImgBox.style.left = this.curIndex * -600 + 'px';
                }

                start() {
                    // 开始自动播放
                    if(this.autoPlay===false){
                        return;
                    }
                    this.stop();
                    this.timer = setInterval(() => {
                        this.next();
                    }, this.autoPlayDuration);
                }

                stop() {
                    // 停止自动播放
                    clearInterval(this.timer);
                }
                handleTransitionEnd() {
                    if (this.curIndex == this.imgUrlList.length - 1) {
                        this.curIndex = 0;
                        this.oImgBox.style.transition = 'none';
                        this.oImgBox.style.left = this.curIndex * -600 + 'px';
                        requestAnimationFrame(() => {
                            requestAnimationFrame(() => {
                                this.oImgBox.style.transition = 'left 0.5s ease-in-out';
                            });
                        })

                    }else if (this.curIndex <= 0) {
                        this.curIndex = this.imgUrlList.length - 1;
                        this.oImgBox.style.transition = 'none';
                        this.oImgBox.style.left = this.curIndex * -600 + 'px';
                        
                        requestAnimationFrame(() => {
                            requestAnimationFrame(() => {
                                this.oImgBox.style.transition = 'left 0.5s ease-in-out';
                            });
                        })
                    }
                    // console.log(this.curIndex);
                    this.trigger(); 
                }
                eff(fn) {
                    this.effect.push(fn);
                }
                trigger() {
                   this.effect.forEach(fn => fn(this.curIndex));
                }
                plugins(...plugins) {
                    plugins.forEach(plugin => {
                        plugin.render && plugin.render(this);
                        plugin.action && plugin.action(this);
                    });

                }
            }

            const selectorBtn = {
                render(carousel) {
                    const oBtnGroup = document.createElement('div');
                    oBtnGroup.className = 'index-selector-btn-group';
                    carousel.app.appendChild(oBtnGroup);
                    const btns = carousel.imgUrlList.slice(0, carousel.imgUrlList.length - 1);
                    btns.forEach((item, index) => {
                        const oBtn = document.createElement('button');
                        oBtn.className = 'btn';
                        if (index === 0) {
                            oBtn.classList.add('active');
                        }
                        oBtnGroup.appendChild(oBtn);
                    })
                    
                },
                updateActive(carousel, curIndex) {
                    console.log(curIndex);
                    if(curIndex === carousel.imgUrlList.length -1)curIndex = 0;
                    const btnGroup = carousel.app.querySelector('.index-selector-btn-group');
                    const btns = btnGroup.querySelectorAll('.btn');
                    btns.forEach((btn, index) => {
                        btn.className = 'btn';
                        if (index === curIndex) {
                            btn.classList.add('active');
                        }
                    })
                },
                action(carousel) {
                    carousel.eff((index) => {
                        this.updateActive(carousel, index);
                    })
                }
            }
            const rightBtn = {
                render(carousel) {
                    const oBtnGroup = document.createElement('div');
                    oBtnGroup.className = 'left-right-btn-group';
                    carousel.app.appendChild(oBtnGroup);
                    const oRightBtn = document.createElement('button');
                    oRightBtn.className = 'btn right';

                    oRightBtn.innerHTML = '&gt;';
                    oBtnGroup.appendChild(oRightBtn);
                   
                },
                action(carousel) {
                    const oRightBtn = carousel.app.querySelector('.right');
                    oRightBtn.addEventListener('click', () => {
                        carousel.stop();
                        carousel.next();
                        carousel.start();
                    });
                }
            }
            const leftBtn = { 
                render(carousel) {
                    const oBtnGroup = document.createElement('div');
                    oBtnGroup.className = 'left-right-btn-group';
                    carousel.app.appendChild(oBtnGroup);
                    const oLeftBtn = document.createElement('button');
                    oLeftBtn.className = 'btn left';
                    oLeftBtn.innerHTML = '&lt;';
                    oBtnGroup.appendChild(oLeftBtn);
                },

                action(carousel) { 
                    const oLeftBtn = carousel.app.querySelector('.left');
                    oLeftBtn.addEventListener('click', () => {
                        carousel.stop();
                        carousel.prev();
                        carousel.start();
                    });
                }
            }
            const imgUrlList = [
                'https://unsplash.com/photos/0dC0h6CjOMM/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjU0NTkwNjM2&force=true',
                'https://unsplash.com/photos/iBmuPRBJj8M/download?ixid=MnwxMjA3fDB8MXxhbGx8MTY0fHx8fHx8Mnx8MTY1NDU4ODkwNg&force=true',
                'https://unsplash.com/photos/rZMiCdPAlss/download?ixid=MnwxMjA3fDB8MXxhbGx8MTg0fHx8fHx8Mnx8MTY1NDU4ODkxMg&force=true',
                'https://unsplash.com/photos/faKvebx79FA/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTh8fG5lb258ZW58MHwwfHx8MTY1NDU5MDcwMw&force=true',
                'https://unsplash.com/photos/0dC0h6CjOMM/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjU0NTkwNjM2&force=true',
            ];
            const oCarousel = document.querySelector('.carousel');

            const carousel = new Carousel({
                el: oCarousel,
                imgUrlList,
            });
            
            carousel.plugins(selectorBtn, rightBtn, leftBtn);
            carousel.start();
        })();


    </script>
</body>

</html>