---
# System prepended metadata

title: JavaScript Slider 輪播圖、燈箱練習
tags: [Example]

---

---
tags: Example,
disqus: hackmd
---

# JavaScript Slider 輪播圖、燈箱練習

這是第一次用Vanilla JS來寫一個輪播+燈箱。

也是第一次寫可以loop的輪播。

所以有很多地方還不夠好，多包涵。

本次參考的輪播燈箱範例[blueimp Gallery](http://blueimp.github.io/Gallery/)

## 程式碼
### html
```htmlmixed=
<html>
    <head>
        <title>Lightbox Image Gallery</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="index.css">
    </head>

    <body>
        <div class="gallery-wrap clearfix">
            <div class="gallery-box">
                <img src="image/small/49924174576_aac29e826c_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49924541758_0898ab1f3b_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49925577287_4b7ec83916_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49926242418_3f21cf8e52_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49928768098_2bef6a3084_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49929403292_389f0233f7_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49929612551_b6dc1392fa_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49929715321_a38d609252_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49929759196_0243bd30b2_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49929923203_aed632ac6c_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49930650242_73129d5e44_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49930937571_62c0709276_s.jpg" alt="">
            </div>
            <div class="gallery-box">
                <img src="image/small/49930991576_a05a942aa0_s.jpg" alt="">
            </div>
        </div>

        <div class="lightbox-wrap">
            <div id="close-btn" class="close-btn">X</div>
            <div id="prev-btn" class="prev-btn control-btn">&lt;</div>
            <div id="next-btn" class="next-btn control-btn">&gt;</div>
            <div class="photo-frame clearfix">
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49924174576_aac29e826c_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49924541758_0898ab1f3b_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49925577287_4b7ec83916_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49926242418_3f21cf8e52_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49928768098_2bef6a3084_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49929403292_389f0233f7_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49929612551_b6dc1392fa_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49929715321_a38d609252_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49929759196_0243bd30b2_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49929923203_aed632ac6c_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49930650242_73129d5e44_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49930937571_62c0709276_b.jpg" alt="">
                </div>
                <div class="photo-box">
                    <img draggable="false" class="photo-box-img" src="image/49930991576_a05a942aa0_b.jpg" alt="">
                </div>
            </div>
            <div class="lightbox-img-wall clearfix">
                <div class="dot-box">
                    <img src="image/small/49924174576_aac29e826c_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49924541758_0898ab1f3b_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49925577287_4b7ec83916_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49926242418_3f21cf8e52_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49928768098_2bef6a3084_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49929403292_389f0233f7_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49929612551_b6dc1392fa_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49929715321_a38d609252_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49929759196_0243bd30b2_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49929923203_aed632ac6c_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49930650242_73129d5e44_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49930937571_62c0709276_s.jpg" alt="">
                </div>
                <div class="dot-box">
                    <img src="image/small/49930991576_a05a942aa0_s.jpg" alt="">
                </div>
            </div>
        </div>
    </body>
    <script src="index.js"></script>
</html>
```

### CSS
```css=
body {
    margin: 0;
    background: #212121;
}
.clearfix:after {
    content: "";
	display: table;
	clear: both;
}

/* 外部圖片牆 */
.gallery-wrap {
    margin: 0 auto;
    width: 975px;
}
.gallery-box {
    float: left;
    width: 75px;
    height: 75px;
    cursor: pointer;
}
.gallery-box img {
    max-width: 100%;
}

/* 燈箱 */
.lightbox-wrap {
    position: fixed;
    left: 0;
    top: 0;
    background-color: #000;
    width: 100%;
    height: 100%;
    z-index: -1;
    opacity: 0;
}
.lightbox-wrap.open {
    animation: openLightBox 1s ease normal both;
}
.lightbox-wrap.close {
    animation: closeLightBox 1s ease normal both;
}
@keyframes openLightBox {
    from {
        z-index: 0;
        opacity: 0;
    }
    to {
        z-index: 0;
        opacity: 1;
    }
}
@keyframes closeLightBox {
    from {
        z-index: 0;
        opacity: 1;
    }
    to {
        z-index: -1;
        opacity: 0;
    }
}
.control-btn {
    position: absolute;
    top: 50%;
    border-radius: 50%;
    border: solid 2px #CCC;
    width: 40px;
    height: 40px;
    cursor: pointer;
    color: #CCC;
    font-size: 20px;
    text-align: center;
    line-height: 40px;
    z-index: 1;
    transform: translate(0, -50%);
}
.control-btn:hover {
    border-color: #FFF;
    color: #FFF;
}
.prev-btn {
    left: 15px;
}
.next-btn {
    right: 15px;
}
.close-btn {
    position: fixed;
    right: 20px;
    top: 20px;
    cursor: pointer;
    color: #CCC;
    font-size: 20px;
    z-index: 1;
}
.close-btn:hover {
    color: #FFF;
}
.lightbox-img-wall {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    margin: 0 auto;
    max-width: 1000px;
    text-align: center;
}
.lightbox-wrap .dot-box {
    position: relative;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    cursor: pointer;
    overflow: hidden;
    display: inline-block;
}
.lightbox-wrap .dot-box.current:after,
.lightbox-wrap .dot-box:hover:after {
    position: absolute;
    left: 0;
    top: 0;
    border: solid 2px #FFF;
    border-radius: 50%;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    content: '';
    display: block;
}
.lightbox-wrap .dot-box img {
    max-width: 100%;
}
.photo-frame {
    height: 100%;
}
.photo-box {
    position: relative;
    top: 0;
    left: 0;
    float: left;
    text-align: center;
    white-space: nowrap;
}
.photo-box .photo-box-img {
    max-width: 100%;
    max-heiht: 100%;
    vertical-align: middle;
}
.photo-box:before {
    width: 0;
	height: 100%;
	content: '';
	display: inline-block;
    vertical-align: middle;
}
.photo-box:after {
    width: 0;
	height: 100%;
	content: '';
	display: inline-block;
    vertical-align: middle;
}
```

### JS
```javascript=
function bindGalleryIndex() {
    var galleryBox = document.getElementsByClassName('gallery-box');

    for (let index = 0; index < galleryBox.length; index++) {
        const element = galleryBox[index];
        
        element.setAttribute('data-index', index);

        handleGalleryClick(element);
    }
}

function bindDotIndex() {
    var dotBox = document.getElementsByClassName('dot-box');

    for (let index = 0; index < dotBox.length; index++) {
        const element = dotBox[index];
        
        element.setAttribute('data-index', index);

        handleDotClick(element);
    }
}

function bindPhotoIndex() {
    var photoBox = document.getElementsByClassName('photo-box');
    var photoFrame = document.getElementsByClassName('photo-frame')[0];

    /* 計算photoFrame總長 */
    photoFrame.style.width = photoBox.length * windowInnerWidth;

    for (let index = 0; index < photoBox.length; index++) {
        const element = photoBox[index];
        
        element.setAttribute('data-index', index);
        element.style.left = -(index * windowInnerWidth);
        element.style.width = windowInnerWidth;

        /* 滑鼠判斷事件 */
        handleMouseMove(photoBox[index]);
    }
}

function handleGalleryClick(galleryPhoto) {
    /* 點擊的時候取目標的data-index */
    galleryPhoto.addEventListener('click', () => {
        const galleryPhotoIndex = parseInt(galleryPhoto.getAttribute('data-index'), 10);
        const lightBox = document.getElementsByClassName('lightbox-wrap')[0];

        /* 更新globalIndex的index */
        globalIndex = galleryPhotoIndex;

        countTranslate(globalIndex);

        dotStyle(globalIndex);

        /* 開啟燈箱 */
        lightBox.classList.remove('close');
        lightBox.classList.add('open');
    });
}

function handleDotClick(dot) {
    /* 點擊的時候取目標的data-index */
    dot.addEventListener('click', () => {
        switchPhotoEnd = false;

        const dotIndex = parseInt(dot.getAttribute('data-index'), 10);

        /* 更新前的globalIndex，也就是dot被點擊前的globalIndex */
        beforeUpdateIndex = globalIndex;

        /* 更新globalIndex，也就是dot被點擊後的globalIndex */
        globalIndex = dotIndex;

        countTranslate(globalIndex, beforeUpdateIndex, true);

        dotStyle(globalIndex);
    });
}

function bindCloseBtn() {
    /* 燈箱內的關閉按鈕 */
    const closeBtn = document.getElementById('close-btn');

    closeBtn.addEventListener('click', () => {
        closeEvent()
    });
}

function closeEvent() {
    /* 關閉燈箱 */
    const lightBox = document.getElementsByClassName('lightbox-wrap')[0];
    lightBox.classList.remove('open');
    lightBox.classList.add('close');
};

function handleMouseMove(element) {    
    let click = false;
    let move = false;
    let xPosition = 0;
    let yPosition = 0;
    let closeStatus = false; /* 關閉燈箱狀態 */
    let horizontalScroll = false; /* 是否為左右滑 */
    let goNext = null; /* 下一張或上一張狀態 */

    element.addEventListener('mousedown', (e) => {
        click = true;

        var e = e || window.event;

        xPosition = e.pageX;
        yPosition = e.pageY;

    }, false);

    element.addEventListener('mousemove', function(e){
        /* 判斷滑鼠為左鍵按下狀態，且切換動畫狀態已結束(true)，滿足兩條件才執行拖拉判斷 */
        if (click && switchPhotoEnd) {
            move = true;

            var photoBox = document.getElementsByClassName('photo-box');
            var photoBoxLength = photoBox.length;
            var e = e || window.event;

            if (e.pageX > xPosition || e.pageX < xPosition) {
                /* 代表左右滑動觸發 */
                var distance = e.pageX - xPosition;
                horizontalScroll = true;

                photoBox[globalIndex].style.transform = 'translate('+ distance +'px, 0px)';
                photoBox[globalIndex].style.transition = transformSec;

                if (e.pageX > xPosition) {
                    /* 往右(前一張)，且判斷是否為第一張 */
                    goNext = false;
                    if (globalIndex === 0) {
                        photoBox[photoBoxLength - 1].style.transform = 'translate('+ (distance - windowInnerWidth) +'px, 0px)';
                        photoBox[photoBoxLength - 1].style.transition = transformSec;
                    } else {
                        photoBox[globalIndex - 1].style.transform = 'translate('+ (distance - windowInnerWidth) +'px, 0px)';
                        photoBox[globalIndex - 1].style.transition = transformSec;
                    }
                } else {
                    /* 往左(下一張)，且判斷是否為最後一張 */
                    goNext = true;
                    if (globalIndex === photoBoxLength - 1) {
                        photoBox[0].style.transform = 'translate('+ (distance + windowInnerWidth) +'px, 0px)';
                        photoBox[0].style.transition = transformSec;
                    } else {
                        photoBox[globalIndex + 1].style.transform = 'translate('+ (distance + windowInnerWidth) +'px, 0px)';
                        photoBox[globalIndex + 1].style.transition = transformSec;
                    }
                }
            } else if ((e.pageY > yPosition) && !horizontalScroll) {
                /* 代表下滑動觸發 */
                element.style.transform = 'translate(0px, '+ (e.pageY - yPosition) +'px)';
                closeStatus = true;
            }
        }
    }, false);

    element.addEventListener('mouseup', (e) => {
        click = false;

        if (move !== true) {
            if (e.eventPhase === 2 && switchPhotoEnd) {
                closeEvent();
            }
        } else {
            move = false;
            switchPhotoEnd = false;

            var photoBox = document.getElementsByClassName('photo-box');
            var photoBoxLength = photoBox.length;

            /* 左右滑的時候會給一個horizontalScroll的狀態 */
            if (horizontalScroll) {
                horizontalScroll = false;
                
                /* 不是去下一張就是回上一張 */
                if (goNext) {
                    if (globalIndex === photoBoxLength - 1) {
                        photoBox[globalIndex].style.transform = 'translate(-'+ windowInnerWidth +'px, 0)';
                        photoBox[0].style.transform = 'translate(0, 0)';
                        photoBox[0].style.transition = transformSec;

                        globalIndex = 0;

                        photoBox[0].addEventListener('transitionend', () => {
                            countTranslate(globalIndex);
                        });
                    } else {
                        photoBox[globalIndex].style.transform = 'translate(-'+ windowInnerWidth +'px, 0)';
                        photoBox[globalIndex + 1].style.transform = 'translate(0, 0)';
                        photoBox[globalIndex + 1].style.transition = transformSec;

                        globalIndex = globalIndex + 1;
                    }
                } else {
                    if (globalIndex === 0) {
                        photoBox[globalIndex].style.transform = 'translate('+ windowInnerWidth +'px, 0)';
                        photoBox[photoBoxLength - 1].style.transform = 'translate(0, 0)';
                        photoBox[photoBoxLength - 1].style.transition = transformSec;

                        globalIndex = photoBoxLength - 1;

                        photoBox[globalIndex].addEventListener('transitionend', () => {
                            countTranslate(globalIndex);
                        });
                    } else {
                        photoBox[globalIndex].style.transform = 'translate('+ windowInnerWidth +'px, 0)';
                        photoBox[globalIndex - 1].style.transform = 'translate(0, 0)';
                        photoBox[globalIndex - 1].style.transition = transformSec;

                        globalIndex = globalIndex - 1;
                    }
                }
                dotStyle(globalIndex);
                photoBox[globalIndex].style.transition = transformSec;
                photoBox[globalIndex].addEventListener('transitionend', () => switchPhotoEnd = true, false);
            }

            /* 在確認為上下滑且非左右滑的時候，mouseup關閉燈箱 */
            if (closeStatus && !horizontalScroll) {
                closeStatus = false;
                closeEvent();
            }
        }
    }, false);
}

function controlBtn() {
    /* 左右按鈕點擊切換圖片 */
    var prevBtn = document.getElementById('prev-btn');
    var nextBtn = document.getElementById('next-btn');
    var photoBox = document.getElementsByClassName('photo-box');
    var photoBoxLength = photoBox.length;

    nextBtn.addEventListener('click', () => {
        switchPhotoEnd = false;

        if (globalIndex === photoBoxLength - 1) {
            photoBox[globalIndex].style.transform = 'translate(-'+ windowInnerWidth +'px, 0)';
            photoBox[globalIndex].style.transition = transformSec;
            photoBox[0].style.transform = 'translate(0, 0)';
            photoBox[0].style.transition = transformSec;

            globalIndex = 0;

            photoBox[0].addEventListener('transitionend', () => {
                countTranslate(globalIndex);
            });
        } else {
            photoBox[globalIndex].style.transform = 'translate(-'+ windowInnerWidth +'px, 0)';
            photoBox[globalIndex].style.transition = transformSec;
            photoBox[globalIndex + 1].style.transform = 'translate(0, 0)';
            photoBox[globalIndex + 1].style.transition = transformSec;

            globalIndex = globalIndex + 1;
        }

        dotStyle(globalIndex);
        photoBox[globalIndex].addEventListener('transitionend', () => switchPhotoEnd = true, false);
    });

    prevBtn.addEventListener('click', () => {
        switchPhotoEnd = false;

        if (globalIndex === 0) {
            photoBox[photoBoxLength - 1].style.transform = 'translate(0, 0)';
            photoBox[photoBoxLength - 1].style.transition = transformSec;
            photoBox[0].style.transform = 'translate('+ windowInnerWidth +'px, 0)';
            photoBox[0].style.transition = transformSec;

            globalIndex = photoBoxLength - 1;

            photoBox[0].addEventListener('transitionend', () => {
                countTranslate(globalIndex);
            });
        } else {
            photoBox[globalIndex].style.transform = 'translate('+ windowInnerWidth +'px, 0)';
            photoBox[globalIndex].style.transition = transformSec;
            photoBox[globalIndex - 1].style.transform = 'translate(0, 0)';
            photoBox[globalIndex - 1].style.transition = transformSec;

            globalIndex = globalIndex - 1;
        }

        dotStyle(globalIndex)
        photoBox[globalIndex].addEventListener('transitionend', () => switchPhotoEnd = true, false);
    });
}

function countTranslate(targetIndex, beforeIndex, transition) {
    /* 計算每一個photo-box的位置 */
    var photoBox = document.getElementsByClassName('photo-box');
    var photoFrame = document.getElementsByClassName('photo-frame')[0];

    photoFrame.style.width = photoBox.length * windowInnerWidth;

    for (let index = 0; index < photoBox.length; index++) {
        const element = photoBox[index];
        const elementIndex = parseInt(element.getAttribute('data-index'), 10);

        element.style.left = -(index * windowInnerWidth);
        element.style.width = windowInnerWidth;

        if (targetIndex > elementIndex) {
            element.style.transform = 'translate(-'+ windowInnerWidth +'px, 0)';
        } else if (targetIndex === elementIndex) {
            element.style.transform = 'translate(0, 0)';
        } else {
            element.style.transform = 'translate('+ windowInnerWidth +'px, 0)';
        }
        element.style.transition = 'transform 0s';
    }

    if (beforeIndex) {
        photoBox[beforeIndex].style.transition = transformSec;
        photoBox[targetIndex].style.transition = transformSec;
    }

    photoBox[targetIndex].addEventListener('transitionend', () => switchPhotoEnd = true, false);
}

function dotStyle(targetIndex) {
    var dotBox = document.getElementsByClassName('dot-box');

    for (let index = 0; index < dotBox.length; index++) {
        dotBox[index].classList.remove('current');
    }

    dotBox[targetIndex].classList.add('current');
}

window.onload = (function() {

    transformSec = 'transform .5s';
    windowInnerWidth = window.innerWidth;
    switchPhotoEnd = true;
    globalIndex = 0;

    bindGalleryIndex();

    bindDotIndex();

    bindPhotoIndex();

    bindCloseBtn();

    controlBtn();

    window.addEventListener('resize', () => {
        windowInnerWidth = window.innerWidth;
        countTranslate(globalIndex);
    });
})();
```
---
###### tags: `Example` `slider` `輪播`