Плавность при scroll'е

577
03 февраля 2017, 06:48

Здравствуйте.

Есть маленький код, который устанавливает значение top у блока в зависимости от положения scroll'а. Проблема в том, что не во всех браузерах оно плавно отображается, т.е. при scroll'е происходят резкие скачки (браузера: IE, Opera <11 и т.д.). Как убрать эти скачки и сделать полностью плавные движения?

Код:

window.onscroll = function() { 
	document.getElementsByTagName('div')[0].style.marginTop = window.pageYOffset + 'px'; 
};
body {height: 2000px; } 
div { 
  width: 200px; 
  height: 200px; 
  background-color: black; 
}
<div></div>

Отображение со скачками:

На картинке маленькие скачки, но с разными объектами и разными браузерами - скачки разного размера.

Ссылка на сторонний редактор: jsfiddle-gjx7ecd4

Answer 1

Поведение блока сильно похоже на fixed, с тем исключением, что при полной прокрутке окна вверх, он маргинится на несколько пикселей. Возможно, решением будет - присвоение ему класса с необходимыми параметрами, если величина прокрутки окна больше его первоначального отступа.

var elem = document.getElementById('test'); //получаем целевой элемент 
var style = elem.currentStyle || window.getComputedStyle(elem);/* тк, если 
напрямую обратиться к свойству style, 
то мы получим список правил для, непосредственно, ЭЛЕМЕНТА, 
нам же нужен именно список правил,  
для таблицы стилей(первое выражение для IE)*/ 
var scrollOffset = style.marginTop;//Собственно, получаем начальный отступ из стилей 
scrollOffset = scrollOffset.substr(0, scrollOffset.length-2)//убираем злосчастные `px` 
window.onscroll = function() { 
  if(window.pageYOffset > scrollOffset){ 
     elem.className = 'scrolled'; 
    return; 
  } 
  elem.className = ''; 
};
body {height: 2000px; padding: 0px; margin: 0px;} 
div#test { 
  width: 200px; 
  height: 200px; 
  background-color: black; 
  margin-top: 7px; 
} 
.scrolled{ 
  position: fixed; 
  margin-top: 0px !important; 
  /*Свойства стилей для id перекрывают свойства для классов, 
 посему так, но для новичков: это BAD PRACTICE!*/ 
  box-sizing: border-box; 
  background-color: red !important; 
  /*Просто, дабы понимать, что сейчас на нем висит класс*/ 
}
<div id='test'></div>

Answer 2

Никакими ухищрениями сделать плавный скролл через onscroll не получится. Проблема в том, что за время поворота колеса мыши вылетает 5-12 событий onwheel, которые трансформируются в onscroll. Ваш код пытается их переварить - и получаются скачки.

Событие scroll не всплывает, поэтому стандартный scroll невозможно отменить через preventDefault.

Возился с этой проблемой довольно долго. Пришлось перехватывать onWheel и анимировать плавно до нужного места. Анимацию делал на jQuery, здесь в рамках ответа на javascript анимацию не ставил - просто скроллинг окна и transfrom: translate.

Важное замечание. Браузер по-разному обрабатывает top и transform, есть примеры в Сети, что transform работает намного более плавно.

Приведенный ниже код перехватывает onWheel, накапливает события в течение 150мс, чтобы отследить полное единичное движение колеса, затем поднимает флажок animating. Во время "анимации" скрипт не воспринимает более никакие новые события скролла, один раз прокручивает окно на полную ранее просуммированную величину скролла, изменяет положение черного квадрата через transform и через 100мс сбрасывает флажок animating.

Эти величины - 150 и 100мс - сугубо эмпирические и подгоняются под конкретную задачу, особенно в том случае, если скрипт осуществляет анимацию элемента.

//    Так квадрат дергается
//    window.onscroll = function() {
//        document.getElementsByTagName('div')[0].style.transform = 'translateY(' + window.pageYOffset + 'px' + ')';
//        return false;
//    };
//  А так - не дергается
var animating = false;
var timeoutID;
function onWheel(event) {
    event = event || window.event;
    var pos = event.deltaY || event.detail || event.wheelDelta;
    console.log(pos);
    if (animating) {
        finalPos = finalPos + pos;
        event.preventDefault();
        return false;
    }
    if (pos > 0) {
        // downscroll code
        if (!animating) {
            // Scroll
            animating = true;
            finalPos = pos;
            event.preventDefault();
            clearTimeout(timeoutID);
            timeoutID = setTimeout(function() {
                console.log('scroll to ' + finalPos);
                window.scrollTo(window.scrollX, window.scrollY + finalPos);
                document.getElementsByTagName('div')[0].style.transform = 'translateY(' + window.pageYOffset + 'px' + ')';
                setTimeout(function() {
                    animating = false;
                }, 100);
            }, 150);
        }
    } else {
        // upscroll code
        if (!animating) {
            // Scroll
            animating = true;
            finalPos = pos;
            event.preventDefault();
            clearTimeout(timeoutID);
            timeoutID = setTimeout(function() {
                console.log('scroll to ' + finalPos);
                window.scrollTo(window.scrollX, window.scrollY + finalPos);
                document.getElementsByTagName('div')[0].style.transform = 'translateY(' + window.pageYOffset + 'px' + ')';
                setTimeout(function() {
                    animating = false;
                }, 100);
            }, 150);
        }
    }
}
if (window.addEventListener) {
    if ('onwheel' in document) {
        window.addEventListener("wheel", onWheel, {passive: false, capture: true}); // IE9+, FF17+, Ch31+
    } else if ('onmousewheel' in document) {
        window.addEventListener("mousewheel", onWheel); // устаревший вариант события
    } else {
        window.addEventListener("MozMousePixelScroll", onWheel); // Firefox < 17
    }
} else {
    window.attachEvent("onmousewheel", onWheel); // IE8-
}

Посмотреть работающий код можно тут в jsfiddle или тут на странице тестового сайта.

READ ALSO
AngularJS: Как записать данные в JSON файл? [требует правки]

AngularJS: Как записать данные в JSON файл? [требует правки]

Подскажите как записать данные в JSON файл? Мне нужно, чтобы после заполнения полей и нажатия на кнопку Save данные сохранялись в JSON файле

398
Как работает NPM модуль request-promise

Как работает NPM модуль request-promise

Подключил модуль request-promiseДля отправки файла использую вот такой код

520
Спрятать элемент с помощью скрипта

Спрятать элемент с помощью скрипта

Есть этот элементКак мне ему сделать display none, если data-date="2017-02-30"

375
JavaScript рекурсия

JavaScript рекурсия

Изучаю JavaScript, рекурсияПример из учебника

368