在函数内部无法设置元素的scrollTop,尽管scrollLeft可以正常设置
我有这么一段代码,用来让你拖拽元素来滚动它。
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; 的组合以及你设置 scrollLeft 和 scrollTop 的方式共同引起的。至少删除CSS中的 scroll-behavior 那一行就能解决。
具体原因尚不清楚,但很明显,你在 elementDrag 函数中使用它自己的数值来设置 scrollTop 的方式似乎并不协调。
不过,我们可以通过事先再存储几个值来对拖拽函数进行一些改进,并改用 scrollTo 函数。
在 dragMouseDown 里存储 scrollLeft 和 scrollTop 的位置,因为这些应该是起始位置。然后在 elementDrag 中从这些位置减去鼠标移动量以得到新坐标。使用 scrollTo 移动到新坐标。之所以使用 scrollTo,是因为它会检查你在CSS中是否设置了 scroll-behavior 属性,并据此表现。
通过这样做,你可以确保只从起始位置按鼠标实际移动的像素数来移动拖拽动作,恰好这么多,不多也不少。你可以通过改变 speed 的值来调整这个倍率(0.5 表示半速,2 表示两倍速)。
作为额外的改进,我对 mousemove 和 touchmove 的事件进行了修改,改用 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>