element.scrollTo 和 element.scrollTop 在 Chrome 和 Firefox 的 window.requestAnimationFrame 循环中不起作用

Posted

技术标签:

【中文标题】element.scrollTo 和 element.scrollTop 在 Chrome 和 Firefox 的 window.requestAnimationFrame 循环中不起作用【英文标题】:element.scrollTo and element.scrollTop not working within a window.requestAnimationFrame loop in Chrome and Firefox 【发布时间】:2021-04-30 15:09:41 【问题描述】:

项目:我设置了一个滚动功能,当单击“向上”或“向下”时,它应该滚动到容器 div 中的下一个项目。它利用 window.requestAnimationFrame() 方法进行循环,并在每次迭代时调用 element.scrollTo。

我这样做的原因是允许自定义持续时间,因为“行为:'smooth'”设置是一个设定的速度,我想保留 css 滚动捕捉功能,所以它需要在一个容器中(所以 window.scrollTo() 是不可能的,尽管这似乎可以跨浏览器工作)。

问题:这在 Safari 中完美运行,但由于某种原因 .scrollTo() 函数无法在 Chrome 和 Firefox 中移动滚动视图。

我的尝试:

    我已尝试设置 element.scrollTop 而不是调用 element.scrollTo() 并得到相同的结果。 我在某处读到 Chrome 的性能对于某些循环来说太快了,所以我用不同持续时间的 setTimeout(()=>,1) 包围了 element.scrollTo()。这也不起作用。

我的代码:代码可在 Codepen.io here查看

html

<div class="scroll-wrap">
  <div id="item-1" class="scroll-item blue">
    <h1>1</h1>
    <div class="scroll-up"></div>
    <div class="scroll-down"></div>
  </div>
  <div id="item-2" class="scroll-item green">
    <h1>2</h1>
    <div class="scroll-up"></div>
    <div class="scroll-down"></div>
  </div>
  <div id="item-3" class="scroll-item red">
    <h1>3</h1>
    <div class="scroll-up"></div>
    <div class="scroll-down"></div>
  </div>
</div>

JS:

const scrollUps = document.querySelectorAll('.scroll-up')
const scrollDowns = document.querySelectorAll('.scroll-down')

// options...
options = 
  scrollWrap: document.querySelector('.scroll-wrap'), // scroll container.
  duration: 300, // Pixels per second. The smaller the number the longer the duration.
  easing: 'easeInOutSine', // can be easeOutSine, easeInOutSine, easeInOutQuint.


// RAF shim from ... http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function()
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback )
            window.setTimeout(callback, 1000 / 60);
          ;
)();

// Click Scroll function
function scrollToY(currentPosition, scrollTargetY) 
    // currentPostion: the current scrollY property of the view within the scroll container
    // scrollTargetY: the target scrollY property of the window
    // speed: time in pixels per second
    // easing: easing equation to use

var scrollY = currentPosition,
    scrollTargetY = scrollTargetY || 0,
    speed = options.duration || 2000,
    easing = options.easing || 'easeOutSine',
    currentTime = 0;

 // *For speed in pixels per second... 
 // min time .1, max time 5 seconds
var time = Math.max(.1, Math.min(Math.abs(scrollY - scrollTargetY) / speed, 5));


// easing equations from https://github.com/danro/easing-js/blob/master/easing.js
const PI_D2 = Math.PI / 2,
    easingEquations = 
        easeOutSine: function (pos) 
            return Math.sin(pos * (Math.PI / 2));
        ,
        easeInOutSine: function (pos) 
            return (-0.5 * (Math.cos(Math.PI * pos) - 1));
        ,
        easeInOutQuint: function (pos) 
            if ((pos /= 0.5) < 1) 
                return 0.5 * Math.pow(pos, 5);
            
            return 0.5 * (Math.pow((pos - 2), 5) + 2);
        
    ;

  // animation loop
  function tick() 
      currentTime += 1 / 60;

      const p = currentTime / time;
      const t = easingEquations[easing](p);

      if (p < 1) 
        requestAnimFrame(tick);
        const newPosition = scrollY + ((scrollTargetY - scrollY) * t)
        setTimeout(()=>
          options.scrollWrap.scrollTo(0, newPosition);
          // console.log('scroll:', options.scrollWrap.scrollTop);
        ,1)
        
        
       else 
        // console.log('scroll done');
        options.scrollWrap.scrollTo(0, scrollTargetY);
      
  

  // call it once to get started
  tick();


// set clickable areas...
Array.from(scrollUps).forEach(btn => 
  btn.addEventListener('click', (e) => 
    // Get Parent of button...
    const parent = e.target.parentElement
    // Get destination element...
    const destId = `item-$Number(parent.id.split('-')[1]) - 1`
    const dest = document.getElementById(destId)
    // call scroll function if destination exists...
    if(dest) 
      const destDistanceToTop = dest.offsetTop
      scrollToY(parent.offsetTop, destDistanceToTop)
    
  )
)

Array.from(scrollDowns).forEach(btn => 
  btn.addEventListener('click', (e) => 
    const parent = e.target.parentElement
    const destId = `item-$Number(parent.id.split('-')[1]) + 1`
    const dest = document.getElementById(destId)
    if(dest) 
      const destDistanceToTop = dest.offsetTop
      // console.log(destDistanceToTop, parent.offsetTop)
      scrollToY(parent.offsetTop, destDistanceToTop)
  
    
  )
)

【问题讨论】:

我可以确认我在 Chrome 中看到了同样的问题,无论是在桌面版还是移动版上。我只能通过在setInterval 调用的单独函数中设置scrollTo 来解决它。 【参考方案1】:

这是由于 CSS 中的滚动捕捉所致。它不能与 requestAnimationFrame 一起工作(出于某种原因)。

我从你的 CSS 中删除了 scroll-snap,没有它似乎也能正常工作。

【讨论】:

以上是关于element.scrollTo 和 element.scrollTop 在 Chrome 和 Firefox 的 window.requestAnimationFrame 循环中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

基于Vue和elemen-ui做一个点击文字显示图片功能并封装成组件

每天一道LeetCode--169.Majority Elemen

在Vue+element 开发中报: The template root requires exactly one elemen 错的解决和原因

10.17 elemen.js

elemen-table表格数据转换-formatter属性

html [css:center-a-position-absolute-element] #css