Плавный скролл используя математическую функцию

Есть вот такой код

let currentScreen = 0

addEventListener('wheel', function(e) {
  if (e.deltaY < 0) currentScreen--
  else currentScreen++

  if (currentScreen < 0) currentScreen = 4
  if (currentScreen > 4) currentScreen = 0
})

function ease(t) {
  return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
}

function ПлавныйСкролл() {
  window.scroll(0, currentScreen * innerHeight)
  requestAnimationFrame(ПлавныйСкролл)
}

requestAnimationFrame(ПлавныйСкролл)
body {
  margin: 0;
  overflow: hidden;
}

.screen {
  height: 100vh;
}

.screen_1 {
  background-color: lightblue;
}

.screen_2 {
  background-color: lightgreen;
}

.screen_3 {
  background-color: tomato;
}

.screen_4 {
  background-color: moccasin;
}

.screen_5 {
  background-color: chocolate;
}
<div class="screen screen_1">1</div>
<div class="screen screen_2">2</div>
<div class="screen screen_3">3</div>
<div class="screen screen_4">4</div>
<div class="screen screen_5">5</div>

и вот такая функция, которую нашел на просторах интернета

easeInOutCubic: t => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1.

Не могу понять, что такое t и как это всё соединить вместе.


Ответы (3 шт):

Автор решения: hu-fo

JS

var startScrollValue = document.documentElement.scrollTop
// чтобы после перезагруки страницы было правильное значение
var currentScreen = Math.floor(startScrollValue / innerHeight)
// значение до которого интерполируется значение переменной i
var finalValue = innerHeight
var duration = 1500
var startTime = null
var endTime = null
var i = 0
var isoktoscroll = true
// здесь будет храниться id которое возвращает функция requestAnimationFrame
// чтобы в последующем отменить её
var rid = null


addEventListener('wheel', function(e) {
  if (!isoktoscroll) return
  isoktoscroll = false

  if (e.deltaY < 0) currentScreen--
  else currentScreen++

  if (currentScreen < 0) currentScreen = 4
  if (currentScreen > 4) currentScreen = 0

  requestAnimationFrame(startScrolling)
})

// эта функция должна принимать аргумент в диапазоне 0-1
function ease(t) {
  // какое-то безумие
  return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
}

function startScrolling(timestamp) {
  startTime = timestamp
  
  endTime = startTime + duration
  ПлавныйСкролл(startTime)
}

function ПлавныйСкролл(now) {
  rid = requestAnimationFrame(ПлавныйСкролл)

  var p = (now - startTime) / duration

  i = startScrollValue + (finalValue * currentScreen - startScrollValue) * ease(p)
  window.scroll(0, i)

  if (now > endTime) {
    // устанавливается конечное значение i
    // чтобы при последующем скролле начать с него
    startScrollValue = i
    // можно снова скроллить
    isoktoscroll = true
    cancelAnimationFrame(rid)
    return
  }
}

// чтобы при изменении высота всё правильно отображалось
onresize = () => {
  finalValue = innerHeight
  startScrollValue = currentScreen * finalValue
  window.scroll(0, startScrollValue)
}
body {
  margin: 0;
  overflow: hidden;
}

.screen {
  height: 100vh;
}

.screen_1 {
  background-color: lightblue;
}

.screen_2 {
  background-color: lightgreen;
}

.screen_3 {
  background-color: tomato;
}

.screen_4 {
  background-color: moccasin;
}

.screen_5 {
  background-color: chocolate;
}
<div class="screen screen_1">1</div>
<div class="screen screen_2">2</div>
<div class="screen screen_3">3</div>
<div class="screen screen_4">4</div>
<div class="screen screen_5">5</div>

Css(по совету этого товарища)

var currentScreen = 0
var isoktoscroll = true

addEventListener('wheel', function(e) {
  if(!isoktoscroll) return
  isoktoscroll = false
  
  if (e.deltaY < 0) currentScreen--
  else currentScreen++

  if (currentScreen < 0) currentScreen = 4
  if (currentScreen > 4) currentScreen = 0
  
  container.style.transform = `translateY(-${currentScreen * 100}vh)`
  
  setTimeout(() => isoktoscroll = true, 1500)
})
body {
  margin: 0;
  overflow: hidden;
}

.screen {
  height: 100vh;
}

.screen_1 {
  background-color: lightblue;
}

.screen_2 {
  background-color: lightgreen;
}

.screen_3 {
  background-color: tomato;
}

.screen_4 {
  background-color: moccasin;
}

.screen_5 {
  background-color: chocolate;
}

.container {
  transition: 1.5s ease-in-out;
}
<div id="container" class="container">
  <div class="screen screen_1">1</div>
  <div class="screen screen_2">2</div>
  <div class="screen screen_3">3</div>
  <div class="screen screen_4">4</div>
  <div class="screen screen_5">5</div>
</div>

→ Ссылка
Автор решения: Qwertiy

~function () {
  var waiting = false

  window.addEventListener('wheel', function (e) {
    if (waiting) return
    waiting = true

    setTimeout(() => { waiting = false }, 500)

    var cur = document.querySelector('.active')
    var parent = cur.parentElement
    var oldAbove = cur.previousElementSibling || parent.lastElementChild
    var oldBellow = cur.nextElementSibling || parent.firstElementChild
    var next = e.deltaY < 0 ? oldAbove : oldBellow
    var newAbove = next.previousElementSibling || next.parentElement.lastElementChild
    var newBellow = next.nextElementSibling || parent.firstElementChild

    oldAbove.classList.remove('above')
    cur.classList.remove('active')
    oldBellow.classList.remove('bellow')

    newAbove.classList.add('above')
    next.classList.add('active')
    newBellow.classList.add('bellow')
  })
}()
html, body, main {
  margin: 0;
  overflow: hidden;
  height: 100%;
}

.screen {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  transform: translateY(100%);
  transition: transform ease-in-out .5s;
  z-index: 1;
}

.screen:last-child {
  z-index: 0;
}

.screen.active, .screen.bellow {
  z-index: 2;
}

.screen.above:last-child {
  z-index: 1;
}

.screen.above {
  transform: translateY(-100%);
}

.screen.active {
  transform: translateY(0);
}
<main>
  <div class="screen active" style="background-color: lightblue;" >1</div>
  <div class="screen bellow" style="background-color: lightgreen;">2</div>
  <div class="screen"        style="background-color: tomato;"    >3</div>
  <div class="screen"        style="background-color: moccasin;"  >4</div>
  <div class="screen above"  style="background-color: chocolate;" >5</div>
</main>

→ Ссылка
Автор решения: Stranger in the Q

Входной аргумент для функции сглаживания обычно называют t, время.

Допустим у Вас уже есть анимация, с линейно меняющимся параметром, при этом время изменяется в интервале от 0 до 1, где 0 - начало анимации, а 1 - конец анимации:

requestAnimationFrame(draw);

function draw(t) {
  t /= 3000; // разделим вермя в миллисикундах, на продолжительность 
             // анимации в миллисекундах, тоесть тут 3 секунды, 
             // иными словани через 3 секунды значение переменной t станет больше 1
             
  t %= 1;    // возьмем остаток от деления на 1, таким образом по прошествии
             // трех секунд, время опять начнется с нуля

  // двигаем круг
  c.setAttribute('transform', `translate(${250*t}, 0)`);
  requestAnimationFrame(draw);
}
<svg style="border: solid"><circle id="c" r="20" cx="20" cy="75" /></svg>


Вот что необходимо сделать чтобы применить функцию сглаживания к времени:

t = easeInOutCubic(t);

let easeInOutCubic = t => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1
requestAnimationFrame(draw);

function draw(t) {
  t /= 3000;        
  t %= 1;
  t = easeInOutCubic(t); // применяем функцию сглаживания к времени
  c.setAttribute('transform', `translate(${250*t}, 0)`);
  requestAnimationFrame(draw);
}
<svg style="border: solid"><circle id="c" r="20" cx="20" cy="75" /></svg>


Или вот пример другой функции сглаживания

let easeElastic = t => Math.pow(2,-10*t) * Math.sin((t-0.3/4)*(2*Math.PI)/0.3) + 1
requestAnimationFrame(draw);

function draw(t) {
  t /= 3000;        
  t %= 1;
  t = easeElastic(t); 
  c.setAttribute('transform', `translate(${250*t}, 0)`);
  requestAnimationFrame(draw);
}
<svg style="border: solid"  width="400" ><circle id="c" r="20" cx="20" cy="75"/></svg>

→ Ссылка