Проблема с самописным слайдером HTML/CSS/JS

101
06 апреля 2021, 18:00

уважаемые знатоки и профессионалы! Из предложенных мне схожих тем по моим запросам на StackOverflow я ничего не нашёл. Возможно, плохо искал или неправильно определял заголовок проблемы. Как бы там не было, вопрос в следующем: Есть самописный слайдер на HTML/CSS/JS (как понятно из заголовка), написанный новичком, то есть мной. Если над слайдером быстро поводить мышкой, то вместо обычной смены слайдов происходит их быстрая прокрутка («пролёт»). В коде вроде бы ошибок нет (иначе бы не работало), но как избавиться от непонятного бага не знаю. Прошу Вашей помощи. Код прилагаю ниже. С уважением, Dmitry

P.S. Вот ссылка на CodePen, - где всё можно проверить в действии. Из-за текста на слайдах не обессудьте;)

<body>
        <div class="container">
          <div class="slider" onmouseenter="handler(event); stop()" onmouseleave="handler(event); start()">
            <div class="slide slide-1 animate"> <!--onmouseenter="stop()" onmouseleave="start()"-->
              <p class="title">HTML5 и CSS3</p>
              <p>Вёрстка:</p>
              <ul>
                  <li>страниц сайтов по PSD и XCF-макетам;</li>
                  <li>вертикальных и горизонтальных меню;</li>
                  <li>форм обратной связи;</li>
                  <li>слайдеров на подобие этого.</li>
                </ul>
            </div>
            <div class="slide slide-2 animate"> <!--onmouseenter="stop()" onmouseleave="start()"-->
              <p class="title">5-6 дней</p>
              <p>Среднее время вёрстки двухстраничного сайта.</p>
            </div>
            <div class="slide slide-3 animate"> <!--onmouseenter="stop()" onmouseleave="start()"-->
              <a href="#" class="btn btn-blue">Успейте сделать заказ</a>
            </div>
            <div class="slider-toggles"> <!--onmouseenter="stop()" onmouseleave="start()"-->
              <a href="#slide-1" class="toggle toggle-1" onclick="setSlide(0, 'gray', 'white')"></a>
              <a href="#slide-2" class="toggle toggle-2" onclick="setSlide(1, 'gray', 'white')"></a>
              <a href="#slide-3" class="toggle toggle-3" onclick="setSlide(2, 'gray', 'white')"></a>
            </div>
          </div>
        </div>
</body>
body {
    padding: 0px;
    margin: 0px;
    font-size: 20px;
    font-family: "Arial", sans-serif;
    line-height: 24px;
    background-color: #D7CCC8;
}
.container {
    position: relative;
    width: 640px;
    min-height: 380px;
    padding: 0px 5px;
    margin: 0 auto;
    /*   outline: 2px solid gray; */
    background-color: #BCAAA4;
}
.slider-toggles {
    position: relative;
    top: 345px;
    width: 85px;
    height: 25px;
    /*  outline: 2px solid gray;  */
    margin: 0 auto;
}
.slider-toggles .toggle {
    position: absolute;
    display: inline-block;
    width: 5px;
    height: 5px;
    border: 10px solid #0088cc;
    border-radius: 50%;
    background-color: #0088cc;
}
.slider-toggles .toggle-1 {
    left: 0px;
}
.slider-toggles .toggle-2 {
    left: 30px;
}
.slider-toggles .toggle-3 {
    left: 60px;
}
.slider-toggles .toggle:focus {
    background-color: white;
}
.slider .slide {
    position: absolute;
    /*z-index: 1;*/
    top: 5px;
    left: 8px;
    width: 575px;
    height: 370px;
    padding: 0px 30px;
    font-weight: bold;
    color: white;
    background-color: #8D6E63;
    opacity: 0;
}
.slider .slide p.title {
    margin-top: 50px;
    font-size: 24px;
}
.slider .slide ul {
    padding: 0px;
    padding-left: 20px;
    margin: 0px;
}
.slider .slide ul > li {
    font-size: 18px;
}
.slider .slide-1 {
    /*z-index: 2;*/
    opacity: 1;
}
.slider .slide-1:after {
    content: "";
    position: absolute;
    top: 20px;
    right: 15px;
    width: 200px;
    height: 290px;
    background: url("https://images.unsplash.com/photo-1556772122-5759ebfd70c1?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ") no-repeat 0 0;
    background-size: 200px auto;
}
.slider .slide-2 p.title {
    margin-bottom: 5px;
}
.slider .slide-2 p {
    margin-top: 10px;
    font-size: 20px;
}
.slider .slide-2:after {
    content: "";
    position: absolute;
    top: 125px;
    left: 175px;
    width: 280px;
    height: 200px;
    background: url("https://images.unsplash.com/photo-1461632830798-3adb3034e4c8?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ") no-repeat 0 0;
    background-size: 280px auto;
}
.btn {
    display: block;
    padding: 15px 10px;
    padding-bottom: 20px;
    margin-top: 255px;
    border: 2px solid gray;
    color: white;
    text-align: center;
    text-decoration: none;
    text-transform: uppercase;
    background-color: gray;
}
.btn-blue {
    border-color: #4A148C;
    background-color: #4A148C;
}
.slider .slide-3 .btn:active {
    color: rgba(255, 255, 255, 0.3);
}
.slider .slide-3:after {
    content: "";
    position: absolute;
    top: 20px;
    left: 150px;
    width: 340px;
    height: 220px;
    background: url("https://images.unsplash.com/photo-1490724500206-cd5482e02b9e?ixlib=rb-1.2.1&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ") no-repeat 0 0;
    background-size: 340px auto;
}
.animate {
    transition: opacity 2750ms ease-in;
}   
//Теперь знаю, что проще было использовать try-catch-finally
function ErrorClass(){
    //EXIT_OUT_LIMITS_OF_ARRAY
    this.ERROR_SLIDE_1 = function(){
        return 100;
    }
    this.ERROR_TOGGLES_1 = function(){
        return 101;
    }
    this.errorCode = function(error_code){
        switch(error_code){
            case this.ERROR_SLIDE_1(): return 'ERROR_EXIT_OUT_LIMITS_OF_ARRAY_CLASS_SLIDE';
            case this.ERROR_TOGGLES_1(): return 'ERROR_EXIT_OUT_LIMITS_OF_ARRAY_CLASS_TOGGLES';
            default: return 'UNKNOWN_ERROR';
        }
    }
}
//Работа со слайдами        
function Slide(){
    var pointer = 0;
    var slides = document.getElementsByClassName("slide");
    var errorClass = new ErrorClass();
    this._checkPointer = function(number){
        if((number >= 0) && (number < slides.length)) {
            return true; 
        } else {
            return false;        
        }
    }
    this.setPointer = function(number){
        if(this._checkPointer(number)){
            pointer = number; 
        }
    }
    this.getPointer = function(){
        return pointer;    
    }
    this.getCurrentSlide = function(){
        return slides[pointer];
    }
    this.getSlidesLength = function(){
        return slides.length;
    }
    this.getSlide = function(number){
        if(this._checkPointer(number)){
            return slides[number];    
        } else {
            return errorClass.ERROR_SLIDE_1();
        }
    }
    this.nextSlide = function(){
        return slides[pointer++];
    }
    this.prevSlide = function(){
        return slides[pointer--];
    }
    this.startNew = function(){
        this.setPointer(0);
    }
}
//Работа с переключателями        
function Toggles(){
    var toggles = document.getElementsByClassName('toggle');
    var errorClass = new ErrorClass();
    var SUCCESSFUL = 0;
    this.getToggles = function(){
        return toggles;
    }
    this._setCurrentToggle = function(pointer, bgCurrent){
        if((pointer < 0) || (pointer >= this.getToggles().length)){
            return errorClass.ERROR_TOGGLES_1();
        } else {
            toggles[pointer].style.backgroundColor = bgCurrent;
            return SUCCESSFUL;
        }
    }
    this._clearToggles = function(bgNoActive){
        var i = 0;
        for(i = 0; i < this.getToggles().length; i++){
            toggles[i].style.backgroundColor = bgNoActive;
        }
    }
    this.activateToggle = function(pointer, bgNoActive, bgCurrent){
        var error;
        this._clearToggles(bgNoActive);
        error = this._setCurrentToggle(pointer, bgCurrent);
        return error;
    }            
}
//status of flag: 0 - слайдер запущен; 1 - остановлен        
function FlagStop(){
    var flag = 0;
    this.setFlag = function(status){
        if((status == 0) || (status == 1)){
            flag = status;
        }
    }
    this.getFlag = function(){
        return flag;
    }
}
var slide = new Slide();
var toggles = new Toggles();
var flagStop = new FlagStop();
var errorClass = new ErrorClass();
//Установка в качестве текущего необходимого слайда            
function setSlide(number, bgNoActive, bgCurrent){
    var i = 0;
    var error_code = 0;
    for(i = 0; i < slide.getSlidesLength(); i++){
        error_code = slide.getSlide(i);
        if(error_code != errorClass.ERROR_SLIDE_1()){
            error_code.style.opacity = "0";
        } else {
            return error_code;
        }
    }
    error_code = slide.getSlide(number)
    if(error_code != errorClass.ERROR_SLIDE_1()){    
        error_code.style.opacity = "1";
    } else {
        return error_code; 
    }
    slide.setPointer(number);
    toggles.activateToggle(number, bgNoActive, bgCurrent);
    return 0;
}
//функция прокрутки
function play() {
    var timerID = 0;
    var err = 0;
    if(flagStop.getFlag() == 1){
        clearTimeout(timerID);
        return;
    }
    if(slide.getPointer() < slide.getSlidesLength()){
        err = setSlide(slide.getPointer(), '#0088cc', 'white');
        if(err) {
            this.alert(errorClass.errorCode(err));
        }
        slide.nextSlide();
    } else {
        slide.startNew();
    }
    if(flagStop.getFlag() == 0){
        timerID = setTimeout(play, 8000);
    }    
}
function stop(){
    flagStop.setFlag(1);
} 
function start(){
    var timerID = 0;
    flagStop.setFlag(0);
    timerID = setTimeout(play, 16000);
}
//в попытках понять/определить проблему
function handler(event){
    console.log(event.type + '[target: ' + event.target.className + '; relatedTarget: ' + event.relatedTarget.className +']\n');
}
window.onload = play();
Answer 1

Всё, что я напишу в данном ответе - предположения, но то, что всё заработало - это факт!
1. Понадобится глобальный timerID. Можно написать класс для работы с ним на javascript в функциональном стиле, чтобы его нельзя было так просто изменить, только через интерфейс.
2. Уберём из функции play() возможность остановки рекурсии и сам рекурсивный вызов. Получим:

function play() {
    var err = 0;
   if(slide.getPointer() < slide.getSlidesLength()){
        err = setSlide(slide.getPointer(), '#0088cc', 'white');
        if(err) {
            this.alert(errorClass.errorCode(err));
        }
        slide.nextSlide();
    } else {
        slide.startNew();
    }
}
  1. Соответственно изменим функции start() и stop(), имеем:
function stop(){
   clearInterval(timerID);
 } 
function start(){
    timerID = setInterval(play, 11000);
}
  1. С другой стороны функция-класс флага нам более не понадобиться, поэтому её можно подредактировать для работы с timerID.
  2. Ну и последнее, запускаем всё это при загрузке страницы:
window.onload = start();

С уважением

P.S. По крайней, мере так намного лучше

READ ALSO
Значение параметра в ответе и в JSON объекте, который спарсил браузер отличается

Значение параметра в ответе и в JSON объекте, который спарсил браузер отличается

В общем передаю через Ajax некоторые параметры и вижу такую картину:

120
Почему функция дает разный результат [закрыт]

Почему функция дает разный результат [закрыт]

Хотите улучшить этот вопрос? Обновите вопрос так, чтобы он вписывался в тематику Stack Overflow на русском

108
Route не рендерит компонент при ручном вводе url адреса

Route не рендерит компонент при ручном вводе url адреса

Делаю небольшое SPA с несколькими страницами, навигацию выполняю средствами react-routerПри клике на <Link /> страницы Route рендерит нужный компонент...

121