在函数内部无法设置元素的scrollTop,尽管scrollLeft可以正常设置

前端开发 2026-07-09

我有这么一段代码,用来让你拖拽元素来滚动它。

elementDrag() 已经能成功设置scrollLeft,但无法设置scrollTop。我可以在其他地方设置该元素的 scrollTop,没问题,而且我也试过直接把它设成一个数字(例如把 elmnt.scrollTop = 1000; 放进 elementDrag() 函数中),但那样也不行。

我感觉这里应该有个很显而易见的问题,但我找不到。

dragElement(document.getElementById("drag-wrapper"));
pageDraggable = true;

function dragElement(elmnt) {
    // drag from anywhere in the element
    elmnt.addEventListener('mousedown', dragMouseDown);
    elmnt.addEventListener('touchstart', dragMouseDown);

    function dragMouseDown(e) {
        if (pageDraggable) {
            e = e || window.event;
            e.preventDefault();
            // get the mouse cursor position at startup:
            startX = (e.clientX || e.targetTouches[0].pageX);
            startY = (e.clientY || e.targetTouches[0].pageY);
            document.addEventListener('mouseup', closeDragElement);
            document.addEventListener('touchend', closeDragElement);
            // call a function whenever the cursor moves:
            document.addEventListener('touchmove', elementDrag);
            document.addEventListener('mousemove', elementDrag);
        }
    }

    function elementDrag(e) {
        if (pageDraggable) {
            e = e || window.event;
            e.preventDefault();
            // calculate the change in cursor position:
            dx = (e.clientX || e.targetTouches[0].pageX) - startX;
            dy = (e.clientY || e.targetTouches[0].pageY) - startY;
            // set the element's new position:
            elmnt.scrollTop = elmnt.scrollTop - (dy / 3);
            elmnt.scrollLeft = elmnt.scrollLeft - (dx / 3);
        }
    }

    function closeDragElement() {
        // stop moving when button/touch is over
        document.removeEventListener('mouseup', closeDragElement);
        document.removeEventListener('touchend', closeDragElement);
        document.removeEventListener('touchmove', elementDrag);
        document.removeEventListener('mousemove', elementDrag);
    }
}
#drag-wrapper {
    position: absolute;
    top: 0px; left: 0px;
    overflow: scroll;
    scroll-behavior: smooth;
    user-select: none;
    background: yellow;
    font-size: 400px;
    width: 700px;
    height: 700px;
}
<body>
    <div id="drag-wrapper">
        lineOne
        <br>lineTwo
        <br>lineThree
    </div>
</body>

解决方案

问题是由使用 scroll-behavior: smooth; 的组合以及你设置 scrollLeftscrollTop 的方式共同引起的。至少删除CSS中的 scroll-behavior 那一行就能解决。

具体原因尚不清楚,但很明显,你在 elementDrag 函数中使用它自己的数值来设置 scrollTop 的方式似乎并不协调。

不过,我们可以通过事先再存储几个值来对拖拽函数进行一些改进,并改用 scrollTo 函数。

dragMouseDown 里存储 scrollLeftscrollTop 的位置,因为这些应该是起始位置。然后在 elementDrag 中从这些位置减去鼠标移动量以得到新坐标。使用 scrollTo 移动到新坐标。之所以使用 scrollTo,是因为它会检查你在CSS中是否设置了 scroll-behavior 属性,并据此表现。

通过这样做,你可以确保只从起始位置按鼠标实际移动的像素数来移动拖拽动作,恰好这么多,不多也不少。你可以通过改变 speed 的值来调整这个倍率(0.5 表示半速,2 表示两倍速)。

作为额外的改进,我对 mousemovetouchmove 的事件进行了修改,改用 Passive events,这提升了滚动的性能,使体验更平滑。尤其是在移除CSS中的 scroll-behavior 属性时,这种效果尤为明显。

const dragWrapper = document.getElementById("drag-wrapper");

const pageDraggable = true;
const speed = 1.0;

dragElement(dragWrapper);

function dragElement(elmnt) {
  let startX = null;
  let startY = null;

  let scrollPositionX = null;
  let scrollPositionY = null;

  elmnt.addEventListener('mousedown', dragMouseDown);
  elmnt.addEventListener('touchstart', dragMouseDown);

  function dragMouseDown(e) {
    if (pageDraggable) { 
      startX = (e.clientX || e.targetTouches[0].pageX);
      startY = (e.clientY || e.targetTouches[0].pageY);

      scrollPositionX = elmnt.scrollLeft;
      scrollPositionY = elmnt.scrollTop;

      elmnt.addEventListener('mouseup', closeDragElement);
      elmnt.addEventListener('touchend', closeDragElement);
      elmnt.addEventListener('touchmove', elementDrag, { passive: true });
      elmnt.addEventListener('mousemove', elementDrag, { passive: true });
    }
  }

  function elementDrag(e) {
    if (pageDraggable) { 
      const dx = (e.clientX || e.targetTouches?.[0].pageX) - startX;
      const dy = (e.clientY || e.targetTouches?.[0].pageY) - startY;

      const x = scrollPositionX - (dx * speed);
      const y = scrollPositionY - (dy * speed);

      elmnt.scrollTo(x, y);
    }
  }

  function closeDragElement() {
    startX = null;
    startY = null;

    elmnt.removeEventListener('mouseup', closeDragElement);
    elmnt.removeEventListener('touchend', closeDragElement);
    elmnt.removeEventListener('touchmove', elementDrag, { passive: true });
    elmnt.removeEventListener('mousemove', elementDrag, { passive: true });
  }
}
#drag-wrapper {
  position: absolute;
  top: 0px;
  left: 0px;
  overflow: scroll;
  user-select: none;
  background: yellow;
  font-size: 400px;
  width: 700px;
  height: 700px;

  /* Remove this line to see the behavior change */
  scroll-behavior: smooth;
}
<div id="drag-wrapper">
  lineOne
  <br>lineTwo
  <br>lineThree
</div>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章