前言

轮播图,基本是前端程序员在学习js的时候,会遇到的一个坎,特别是无缝轮播,此时的心情莫过于此。 心情

布局与CSS

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Message</title>
    <link rel="stylesheet" href="//at.alicdn.com/t/font_1616345_0cfsyvveggbc.css">
    <style>
        .container {
            width: 800px;
            margin: 100PX auto 0;
            box-shadow: 0 0 4px 1px rgba(232, 237, 250, .5);
            padding: 24px;
            border: 1px solid #ebebeb;
            border-radius: 3px;
        }

        li {
            list-style: none;
        }

        .carousel {
            position: relative;
            text-align: center;
            height: 300px;
            overflow: hidden;
        }

        .panels > a {
            position: absolute;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            background-color: #d3dce6;
            color: #fff;
            text-decoration: none;
            font-weight: 500;
            font-size: 18px;
        }
        .panels > a.active {
            z-index: 10;
        }

        .panels > a:nth-child(2) {
            background-color: pink;
        }

        .panels > a:nth-child(3) {
            background-color: burlywood;
        }

        .panels > a:nth-child(4) {
            background-color: lightblue;
        }

        .panels > a.active {
            z-index: 10;
        }

        .carousel:hover .arrow i:nth-child(odd) {
            left: 15px;
            opacity: 1;
        }

        .carousel:hover .arrow i:nth-child(even) {
            right: 15px;
            opacity: 1;
        }

        .arrow > i {
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            background-color: rgba(31, 45, 61, .1);
            width: 36px;
            height: 36px;
            border-radius: 50%;
            display: flex;
            justify-content: center;
            align-items: center;
            color: #fff;
            font-size: 12px;
            cursor: pointer;
            transition: .3s;
            opacity: 0;
            z-index: 99;
        }

        .arrow > i:hover {
            background-color: rgba(31, 45, 61, .2);
        }

        .arrow i:nth-child(1) {
            left: -20px;
        }

        .arrow i:nth-child(2) {
            right: -20px;
        }

        .indicators {
            position: absolute;
            bottom: 0;
            left: 50%;
            transform: translateX(-50%);
            z-index: 99;
        }

        .indicators li {
            display: inline-block;
            padding: 12px 4px;
            cursor: pointer;
        }

        .indicators li > button {
            background-color: #fff;
            height: 3px;
            width: 30px;
            border: 0;
            outline: none;
            cursor: pointer;
            transition: .3s;
            opacity: .58;
        }

        .indicators li.active > button {
            opacity: 1;
        }
    </style>
</head>

<body>
<div class="container">
    <h2>Carousel</h2>
    <div class="carousel">
        <div class="panels">
            <a class="active" href="#">1</a>
            <a href="#">2</a>
            <a href="#">3</a>
            <a href="#">4</a>
        </div>
        <div class="arrow">
            <i class="iconfont icon-arrow-left"></i>
            <i class="iconfont icon-arrow-right"></i>
        </div>
        <ul class="indicators">
            <li class="active">
                <button></button>
            </li>
            <li>
                <button></button>
            </li>
            <li>
                <button></button>
            </li>
            <li>
                <button></button>
            </li>
        </ul>
    </div>
</div>
</body>
</html>

js逻辑

使用es6 class的写法实现

const css = ($node, cssObj) => Object.assign($node.style, cssObj)
    const animation = {
        slice($from, $to, direction) {
            console.log('slice', arguments)
            $from.style = ''
            $to.style = ''
            css($from, {
                transform: `translateX(0)`,
                zIndex: 10
            })
            css($to, {
                transform: `translateX(${direction === 'right' ? '-' : ''}100%)`,
                zIndex: 10
            })
            setTimeout(() => css($from, {
                transform: `translateX(${direction === 'left' ? '-' : ''}100%)`,
                transition: `.4s`
            }))
            setTimeout(() => css($to, {
                transform: `translateX(0)`,
                transition: `.4s`
            }))
        },
        zoom($from, $to) {
            console.log('zoom', arguments)
            $from.style = ''
            $to.style = ''
            css($from, {
                transform: `scale(1)`,
                zIndex: 10
            })
            css($to, {
                transform: `scale(0)`,
                zIndex: 10
            })
            setTimeout(() => css($from, {
                transform: `scale(0)`,
                transition: `.4s`
            }))
            setTimeout(() => css($to, {
                transform: `scale(1)`,
                transition: `.4s`,
            }))
        },
        vertical($from, $to, direction) {
            console.log('vertical', arguments)
            $from.style = ''
            $to.style = ''
            css($from, {
                transform: `translateY(0)`,
                zIndex: 10
            })
            css($to, {
                transform: `translateY(${direction === 'right' ? '-' : ''}100%)`,
                zIndex: 10
            })
            setTimeout(() => css($from, {
                transform: `translateY(${direction === 'left' ? '-' : ''}100%)`,
                transition: `.4s`
            }))
            setTimeout(() => css($to, {
                transform: `translateY(0)`,
                transition: `.4s`
            }))
        }
        // 需要动画特效在此添加即可
    }

    class Carousel {
        /**
         * $root [Node] 轮播组件的父节点
         * mode [String] 动画效果  可选slice-zoom
         * duration [Number] 动画运动时长 *未实现
         * */
        constructor($root, mode, duration, delay) {
            this.$root = $root;
            this.$$panels = $root.querySelectorAll('.panels a');
            this.$next = $root.querySelector('.arrow .icon-arrow-right');
            this.$pre = $root.querySelector('.arrow .icon-arrow-left');
            this.$$indicators = $root.querySelectorAll('.indicators li');
            // 循环定时
            this.timer = null
            this.delay = delay || 2000
            // 动画效果
            this.mode = mode
            this.loopPageHandel()
            // 绑定事件
            this.bind();
        }

        bind() {
            this.$next.onclick = this.nextPageHandel.bind(this);
            this.$pre.onclick = this.prePageHandel.bind(this);
            this.$$indicators.forEach($indicator => $indicator.onclick = this.indicatorsPageHandel.bind(this))
            // 循环轮播
            this.$root.onmouseover = () => clearInterval(this.timer)
            this.$root.onmouseleave = () => this.loopPageHandel()
        }

        getIndex() {
            return [...this.$$indicators].indexOf(this.$root.querySelector('.indicators li.active'))
        }

        getPreIndex() {
            return (this.getIndex() - 1 + this.$$panels.length) % this.$$panels.length
        }

        getNextIndex() {
            return (this.getIndex() + 1) % this.$$panels.length
        }

        setPage(fromIndex, toIndex, direction) {
            const $fromNode = this.$$panels[fromIndex]
            const $toNode = this.$$panels[toIndex]
            animation[this.mode]($fromNode, $toNode, direction)
        }

        setActive(index) {
            this.$$indicators.forEach($indicator => $indicator.classList.remove('active'))
            this.$$indicators[index].classList.add('active')
            this.$$panels.forEach($panel => $panel.classList.remove('active'))
            this.$$panels[index].classList.add('active')
        }

        nextPageHandel() {
            let toIndex = this.getNextIndex(),
                fromIndex = this.getIndex()
            this.setActive(toIndex)
            this.setPage(fromIndex, toIndex, 'left');
        }
        prePageHandel() {
            let toIndex = this.getPreIndex(),
                fromIndex = this.getIndex()
            this.setActive(toIndex)
            this.setPage(fromIndex, toIndex, 'right');
        }

        indicatorsPageHandel(e) {
            const $clickNode = e.target.nodeName === 'BUTTON' ? e.target.parentElement : e.target
            let toIndex = [...this.$$indicators].indexOf($clickNode),
                fromIndex = this.getIndex()
            if(toIndex === fromIndex) return
            const direction = fromIndex > toIndex ? 'right' : 'left'
            this.setPage(fromIndex, toIndex, direction);
            this.setActive(toIndex)
        }
        loopPageHandel() {
            this.timer = setInterval(() => {
                this.nextPageHandel()
            }, this.delay)
        }
    }

    const $carousel = document.querySelector('.carousel');
    new Carousel($carousel, 'vertical')