Реализация плавной прокрутки (часть 1)

JavaScript

предисловие

Изучив плавную прокрутку в течение 2 дней, а затем совместив реализацию точки привязки, я чувствую, что много набрал, поэтому записал запись, чтобы это организовать.

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

Поэтому нам нужна реализация плавной прокрутки.

Примечание. При использовании codepen среда браузера codepen — это среда вашего браузера, поэтому, если следующий пример не работает, проверьте среду вашего браузера.

Реализовано с помощью css

Для более новых браузеров мы, наконец, дождались нативной реализации css и больше не нуждались в js для достижения плавной прокрутки.

css-свойствоscroll-behaviorрядомnavigationилиCSSOM scrolling APIsВступает в силу триггерная прокрутка. когда его значениеsmooth, нажмите на метку соответствующей точки привязки, и произойдет плавная и равномерная прокрутка до соответствующей позиции точки привязки. Поскольку поведение якоряCSSOM scrolling APIsвызывать

Узнайте больше о MDNscroll-behavior

Чтобы он вступил в силу во всем html, нам просто нужноcssЭто нормально писать так:

html {
  scroll-behavior: smooth; 
}

Примечание. Недопустимо использовать это свойство в теле, подробности см. в mdn.

Пример на codePen

Chrome поддерживает это свойство, начиная с версии Chrome 61 (2017.9), поэтому видно, что это свойство является относительно новым и поддерживается только более новыми версиями Chrome, Firefox и Opera. И ни одна версия Safari, Edge, IE его не поддерживает.

Используйте window.scrollTo()

window.scrollTo имеет два применения

window.scrollTo(x-coord, y-coord)
window.scrollTo(options)

Первое использование похоже на обычную точку привязки, немедленно прокручивая до соответствующей позиции. Только второе использование может обеспечить плавную прокрутку, когда входящиеoptionsвнутреннийbehavior:smooth, который может обеспечить равномерную и плавную прокрутку

window.scrollTo({
  top: 100, // 滚动终点y的位置
  left: 100, // 滚动终点x的位置
  behavior: 'smooth' //平滑滚动
});

Пример на codePen

К сожалению, второе использование также менее совместимо.

Поддерживаются только более новые версии Chrome, Firefox, Opera. И ни одна версия Safari, Edge, IE его не поддерживает.

Реализовано с помощью requestAnimationFrame

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

requestAnimationFrameЭто API, предоставляемый браузером для рисования анимации. Он будет выполняться перед каждым кадром Layout и Paint в браузере, который часто вызывается перед перекомпоновкой и перерисовкой.Это упрощает выполнение куска кода, меняющего стиль, и тогда браузер может сразу перекомпоновывать, перерисовывать и сгенерировать один кадр.

Преимущества использования requestAnimationFrame

В настоящее время лучше использовать requestAnimationFrame для достижения

Каковы преимущества использования реализации requestAnimationFrame?

  1. По сравнению с предыдущей реализацией setInterval, requestAnimationFrame имеет лучшую производительность. requestAnimationFrame гарантирует, что перекомпоновка и перерисовка выполняются после каждого изменения стиля, а setInterval может заставить браузер выполнять недопустимые перекомпоновку и перерисовку. requestAnimationFrame будет срабатывать в жизненном цикле каждого кадра, что сделает анимацию более плавной, а setInterval не может гарантировать, что сработает каждый кадр.

  2. Совместимость requestAnimationFrame очень хорошая и может быть обратно совместима с IE10. Для IE9 и ниже вы можете понизить полифилл, который использует setTimeout или setInterval для реализации requestAnimationFrame.

  3. Вы можете написать свой собственный для достижения различных скоростей прокрутки, вы можете добиться линейной скорости, вы также можете добиться эффекта сначала ускорения, а затем замедления.

raf реализует единую прокрутку

Ниже приведен пример попытки добиться этого с помощью requestAnimationFrame.

В сочетании с этим примером, кстати, давайте представим использование requestAnimationFrame

сначала поdocument.documentElement.scrollTopПолучить текущую позицию полосы прокрутки страницы.

document.documentElement.scrollTop представляет расстояние от полосы прокрутки до верхней части страницы.Если document.documentElement.scrollTop === 0, это означает, что полоса прокрутки находится в верхней части страницы.

document.documentElement.scrollTop //
document.documentElement.scrollTop = 0 //对其赋值,会使滚动条立刻滚动到页面最顶端

Далее воспользуемся requestAnimationFrame, чтобы получить простейший пример равномерной прокрутки:

<div id="button">回到顶部</div>
let button = document.getElementById('button')
button.addEventListener('click',function(){
  let cb = ()=>{
    if(document.documentElement.scrollTop===0){
      //满足条件,不再递归调用
      return;
    } else {
      let speed = 40 //每个帧内滚动条移动的距离
      document.documentElement.scrollTop -= speed
      //不满足条件,再次调用cb
      requestAnimationFrame(cb)
    }
  }
  requestAnimationFrame(cb)
}

requestAnimationFrame принимает функцию обратного вызова, которая будет выполнена где-то в следующем кадре браузера. Обратите внимание, что есть только следующий кадр, поэтому в функции обратного вызова необходимо выполнить условное суждение.Когда условие не выполняется, функция обратного вызова снова передается в raf.

Вы можете проверить этот пример на codePen:codePen

В примере реализована равномерная прокрутка, а скорость прокрутки можно регулировать по переменным.

Далее рассмотрим функцию, нажмем кнопку, где бы ни была полоса прокрутки, она может вернуться в начало страницы примерно за 2 секунды, как это должно быть реализовано? И анимация равномерного движения кажется немного жестковатой, можно ли добиться функции сначала разгона, а потом торможения?

Raf реализует прокрутку вверх с переменной скоростью и контролирует время прокрутки до 2 секунд.

1. Как прокрутить на месте за 2 секунды?

Вообще говоря, наиболее распространенная частота обновления аппаратного устройства составляет 60 кадров, и браузеру не имеет смысла выполнять перерисовку перерисовки между аппаратными 2 кадрами. Следовательно, продолжительность одного кадра браузера составляет 16,7 мс.Грубо можно считать, что время одного кадра браузера составляет 1/60 с, и он вызывается 120 раз непрерывно.raf(cb)Примерно 2 секунды.

2. Как добиться прокрутки с переменной скоростью?

Расстояние прокатки фиксировано, пусть будетdistance, нам нужно сделать это в 120 кадрахdocument.documentElement.scrollTopзначение уменьшается до 0.

const distance = document.documentElement.scrollTop

Во время этой анимации скорость — это значение, вычитаемое из scrollTop в одном кадре. Понимая это, мы можем использовать арифметическую последовательность для реализации прокрутки с переменной скоростью.

//假设最小的步长为x
第1帧 document.documentElement.scrollTop - x * 1
第2帧 document.documentElement.scrollTop - x * 2
第3帧 document.documentElement.scrollTop - x * 3
...
第60帧 document.documentElement.scrollTop - x * 60
第61帧 document.documentElement.scrollTop - x * 60 //速度达到峰值
第62帧 document.documentElement.scrollTop - x * 59
第63帧 document.documentElement.scrollTop - x * 58
...
第120帧 document.documentElement.scrollTop - x * 1

так

let x = distance / (1+2+3···+60+60+59+···+2+1)
相当于
let x = distance / 60 / 61

Полный код js ниже также подсчитывает время прокрутки.

let button = document.getElementById('button')

let dateBegin; 
let dateEnd;
button.addEventListener('click',function(){
  dateBegin = Date.now()
  let current = document.documentElement.scrollTop
  // 使用promise用来统计时间
  new Promise((resolve,reject)=>{
    let unit = current/60/61 //最小步长
    let index = 0
    let cb = ()=>{
      if(document.documentElement.scrollTop===0){
        dateEnd = Date.now()
        resolve([dateBegin,dateEnd])
      } else{
        index++
        if(index<=60){
          current-=index*unit 
          //这个地方不要用document.documentElement.scrollTop代替current,
          //document.documentElement.scrollTop可以接受浮点数,之后会转化为整数
          //这样的话会使时间变的不准
        } else if(index>60){
          current-=(121-index)*unit
        }
        document.documentElement.scrollTop = current
        requestAnimationFrame(cb)
      }
    }
    requestAnimationFrame(cb)
  }).then((data)=>{
    console.log(data[1] - data[0],' ms')
  })
})

кодPenViewcodePen

Вывод консоли составляет около 1900 мс, и видно, что прокрутка сначала ускоряется, а затем замедляется.

Однако реализация контроля времени таким образом зависит от устройства, чтобы обеспечить обновление кадров 60. Если текущая страница имеет большое количество анимационных рендерингов или сложных вычислений js, номер кадра браузера не может гарантировать запуск 60 кадров?

На самом деле использование raf здесь довольно грубое, например, raf предоставит временной параметр для входящего cb, который может иметь точность до 5 мкс. Используйте это, чтобы точно контролировать время прокрутки. Вы также можете добавить расширенные функции, вы можете вызватьcancelAnimationFrameчтобы отменить выполнение raf, тем самым прервав прокрутку во время прокрутки.

В следующей статье будет анализsmooth-scrollИсходный код этой библиотеки, реализация этой библиотеки очень захватывающая, она поддерживает множество методов прокрутки с переменной скоростью и может быть хорошо настроена. В документе API vue нажмите на боковую панель, и документ плавно прокрутится до позиции соответствующего API, который реализован с помощью этой библиотеки. Оставь пока это здесь.

В крайнем случае: используйте setInterval или setTimeout для достижения

Можно сказать, что это использование является наиболее совместимым методом. Когда браузер не поддерживает raf, мы можем использовать полифилл raf, реализованный с помощью setTimeout. Это похоже на то, как пишется raf, поэтому я не буду вдаваться в подробности. Если вам нужна совместимость с браузерами, не поддерживающими raf, лучше всего использовать полифилл raf, который не только гарантирует плавную деградацию, но и лучше работает в современных браузерах.