Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Как устроена логика прокрутки колёсиком(скролла)? (https://javascript.ru/forum/misc/77564-kak-ustroena-logika-prokrutki-koljosikom-skrolla.html)

drkrol 20.05.2019 22:56

Как устроена логика прокрутки колёсиком(скролла)?
 
Здравствуйте. Делаю свою кастомную прокрутку и столкнулся с такой проблемой: не могу понять, как работает скролл, а точнее "что происходит при кручении колёсика дважды и более".

Сейчас у меня есть такое:
https://jsfiddle.net/drkrol/mtvhrdgb/7/

Загвоздка вот в этом коде:
// условие при двойном кручении
    if (per1 == 1) {
     step = step + shag;
    } else {
     step = shag;
    }


Логика такая. Есть per1. При начале кручения он принимает значение 1, в конце анимации срабатывает код onComplete: function() {per1 = 0;}. При условии, когда per1 == 1 размер расстояния увеличивается на 1 шаг.

Проблема заключается в том, что если крутить колёсиком на 0.5 секунде (скорость анимации = 0.7), то через несколько таких прокруток вы улетите далеко вниз.

Если совсем убрать условие IF, то прокрутка будет рывками, а мне нужна плавность.

Вообще я стремлюсь вот к такому результату: http://maxlepinskih.com/prices , но я не понимаю, как это сделано у него. В его коде разобраться не смог.

И ещё нюанс один. Мне, чтобы оно плавно работало, нужно было убрать overflow у body, а в примере по ссылке у него плавно и сколлбар есть...

Скажите, как должно быть устроено условие IF, чтобы получилось, как у maxlepinskih?

рони 21.05.2019 00:15

drkrol,
скролл по блочно

Malleys 21.05.2019 03:17

рони, что это?
Цитата:

Сообщение от рони
    $(window).on("mousewheel DOMMouseScroll", function(c) {
        c.preventDefault();

К сожалению ваше решение блокирует прокрутку. Для прокрутки следует использовать событие scroll, которое происходит, когда содержимое элемента прокручивается, независимо от того, каким способом это происходит (вращение колёсика мыши, зажали среднюю кнопку мыши, перетаскиваем полосу прокрутки, жест прокрутка на сенсорном экране, клавиши Вверх/Вниз/Пробел/PgUp/PgDown/Home/End, прокрутка при помощи touchpad и ещё множество других способов). Вы же всё отменили, и ещё написали body { overflow: hidden; }. Это не прокрутка по блокам, а вращение колёсика мыши вызывает перемещение скрытой полосы прокрутки! Обратите внимание, что работает только при вращении колёсика мыши.

И ещё, чтобы избежать подёргивания и блокировки полосы прокрутки, не следует менять свойство scrollTop/ScrollLeft, прокрутка тогда очень не естественная получается. И странно получается, когда полоса прокрутки пытается вырваться из под курсора мыши. (поскольку происходит подмена scrollTop/ScrollLeft) Правильно делать так, чтобы при чтении scrollTop/ScrollLeft, менялся вид, а не сами эти свойства!

drkrol,
Цитата:

Сообщение от drkrol
Мне, чтобы оно плавно работало

Можно сделать так, чтобы сначала при прокрутке ничего не менялось, а затем при помощи своей формулы рассчитать положение содержимого
https://jsfiddle.net/u9xw6tjy/2/

рони 21.05.2019 09:59

Malleys,
спасибо за науку.

рони 21.05.2019 11:37

плавный scroll
 
Malleys,
насколько правильно, сделать например, так?


<!DOCTYPE html>
<html>
<head>
  <title>jQuery scroll example</title>

  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
  <style>
body {
  background: #20262E;
  font-family: Helvetica Neue;
}

.wrap-box {
  width: 290px;
  height: 290px;
  background: #777;
  margin: 10px;
}

body, html {
  height: 100%;
  margin: 0;
}

.wrap {
  overflow: auto;
  border: 1px solid red;
  box-sizing: border-box;
  height: 100%;
}

.wrap-content {
  overflow: hidden;
}

.boxes{
    transition: transform 1.8s cubic-bezier(0.47, 0, 0.745, 0.715);
}

  </style>
<script>
window.onload = function() {
  for (var i = 0; i < 100; i++) {
    $(".boxes").append('<div class="wrap-box">' + (1 + i) + "</div>");
  }
  var _top = 0;
  var top = 0;
  var scrollTop = 0;
  var timer;
  $(".wrap").scroll(function() {
    _top = this.scrollTop;
    scrollTop = _top - top;
    window.clearTimeout(timer);
    $(".boxes").css({"transform":"translateY(" + scrollTop + "px)", "transitionDuration":"0ms"});
    timer = window.setTimeout(function() {
      $(".boxes").css({"transform":"translateY(0px)", "transitionDuration":""});
      top = _top;
    }, 100);
  });
};
</script>

</head>
<body>
    <div class="wrap">
  <div class="wrap-content">
    <div class="boxes"></div>
  </div>
</div>
</body>
</html>

drkrol 23.05.2019 02:05

Malleys,
Спасибо за код. Обычно меня пытаются научить уму разуму, тут же вы мне сразу готовое решение предоставили. Ещё раз спасибо, но я не совсем в нём разобрался. Скажите пожалуйста, как мне регулировать время анимации (скорость) и расстояние одного шага? И чёт и туда, и сюда...

Если у вас будет время и если вам не сложно, то не могли бы вы чуть подробнее разъяснить свой код? Я просто хотел вникнуть. Я понял, что всё держится на performance.now(), который возвращает время. Но как время помогает с плавностью я так и не понял...

upd. Проверил ваш вариант в Microsoft Edge. В начале анимации есть небольшое подёргивание, которое не наблюдается в Chrome и Opera

https://yadi.sk/i/YDtQ42kBiSpm4w - видео. Лучше скачать его (860 кб весит), иначе непонятно, где лаги

Malleys 23.05.2019 17:13

Цитата:

Сообщение от drkrol
Скажите пожалуйста, как мне регулировать время анимации (скорость) и расстояние одного шага?

В этом примере вы можете только регулировать время анимации в той строчке, где написано...
var a = 1 - Math.exp(-0.005 * dt);
Число 0.005 поделите на нужное кол-во секунд, или умножьте на число, во сколько раз быстрее хотите!

Цитата:

Сообщение от drkrol
upd. Проверил ваш вариант в Microsoft Edge. В начале анимации есть небольшое подёргивание...

Да, я посмотрел ваше видео, формула, по которой вычисляется позиция, представлена гладкой кривой, поэтому при идеально правильно рендеринге не должно быть никакого подёргивания. Это ошибка/недочёт движка в Microsoft Edge. По крайней мере следующая версия Microsoft Edge будет основана на webkit и эта проблема, скорей всего, исчезнет! На самом деле, те, кто используют обычно этот браузер сейчас ничего особенного не заметят, поскольку и другие анимации слегка подёргиваются!

Цитата:

Сообщение от drkrol
Если у вас будет время и если вам не сложно, то не могли бы вы чуть подробнее разъяснить свой код? Я просто хотел вникнуть. Я понял, что всё держится на performance.now(), который возвращает время. Но как время помогает с плавностью я так и не понял...

На самом деле performance.now() нужен только для того, чтобы вычислять время между двумя отрисовками анимации. Время между двумя кадрами.

Все держится на формуле var a = 1 - Math.exp(-0.005 / T * dt);, которая и вычисляет, как быстро приближаться к конечной позиции прокрутки.

Цитата:

Сообщение от drkrol
Скажите пожалуйста, как мне регулировать время анимации (скорость) и расстояние одного шага?

Расстояние одного шага можно регулировать так: нужно уменьшить область прокрутки в A раз, тогда до конца области прокрутки мы будем добираться в A раз быстрее. Но само содержимое будет прокручиваться с такой же скоростью, как обычно. Поэтому мы его будем в A раз дальше продвигать вдоль оси прокрутки. Вот пример... вынес время анимации и во сколько раз ускорить обычную прокрутку в отдельные переменные в начале
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width">
	<title>Document</title>
	<style>
		body {
			background: #20262E;
			font-family: "Helvetica Neue", system-ui;
		}

		.wrap-box {
			width: 290px;
			height: 290px;
			background: #777;
			margin: 10px;
		}

		body,
		html {
			height: 100%;
			margin: 0;
		}

		.wrap {
			overflow: auto;
			border: 1px solid red;
			box-sizing: border-box;
			height: 100%;
		}

		.wrap-content {
			overflow: hidden;
		}
	</style>
</head>

<body>
	<div class="wrap">
		<div class="wrap-content">
			<div class="boxes"></div>
		</div>
	</div>

	<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
	<script>
		$(function() {
			for (var i = 0; i < 100; i++) {
				$(".boxes").append(`<div class="wrap-box">${1 + i}</div>`);
			}

			var T = 0.75; // время до торможения (секунды)
			var A = 4; // во сколько раз уменьшить полосу прокрутки? Лучше всего [1;5]

			var _top = 0;
			var top = 0;
			var height;
			var originalHeight, wrapHeight;
			var scrollTop = 0;
			var lastTime = performance.now();
			var isScrolling = false;
			
			var wrap = $(".wrap");
			var wrapContent = $(".wrap-content");
			var boxes = $(".boxes");

			wrap.scroll(function() {
				_top = this.scrollTop;
				wrapHeight = wrap.height();
				if (!isScrolling) loop();
			});
			
			$(window).resize(function() {
				wrapHeight = wrap.height();
				originalHeight = wrapContent.css("height", "").height();
				height = Math.min(originalHeight - wrapHeight, (originalHeight - wrapHeight) / A) + wrapHeight;
				
				wrapContent.css("height", (height) + "px");
				wrap.scroll();
			});
			
			$(window).resize();

			function loop() {
				isScrolling = true;
				var dt = -lastTime + (lastTime = performance.now());

				var a = 1 - Math.exp(-0.005 / T * dt);
				var p = _top / (height - wrapHeight);

				top = (1 - a) * top + a * _top;

				scrollTop = _top - top;
				scrollTop = Math.abs(scrollTop) < 0.01 ? 0 : scrollTop;
				boxes.css("transform", "translateY(" + (scrollTop - p * (A - 1) / A * (originalHeight - wrapHeight)) + "px)");

				if (scrollTop !== 0)
					requestAnimationFrame(loop);
				else
					isScrolling = false;
			}
		});
	</script>
</body></html>


Цитата:

Сообщение от рони
насколько правильно, сделать например, так?

Всё ОК, коме того, что если крутануть колёсико мыши и не дождавшись конца анимации ещё раз крутануть, то это приведёт к сдвигу к начальной какой-то позиции, т. е. к тому моменту, с которого анимация начиналась, и уже с того места запустится новая анимация, вызванная кручением колёсика мыши. (ну или любым другим способом прокрутить, но не дождаться конца анимации)

рони 23.05.2019 18:14

Цитата:

Сообщение от Malleys
ещё раз крутануть, то это приведёт к сдвигу

почему в вашем коде нет этого сдвига?

рони 23.05.2019 19:04

Цитата:

Сообщение от Malleys
формула, по которой вычисляется позиция, представлена гладкой кривой,

не понимаю, как это работает, если
Цитата:

Сообщение от Malleys
var a = 1 - Math.exp(-0.005 / T * dt);

это формула, то зачем нужно Math.exp, почему например не a = 0.0023 * dt;
в целом плохо понимаю что происходит в фунции loop, кроме того что scrollTop стремится к нулю.

drkrol 24.05.2019 00:28

чудеса какие-то... ничего не понял...

В firefox, кстати, шаг одного прокручивания в 2 раза меньше. Даже в 2.5...

Может если за место
"transform", "translateY"
сделать GSAP, то подёргиваний не будет?


Часовой пояс GMT +3, время: 18:34.