为什么在 mousemove 上设置 scrollLeft 和 scrollTop 会导致 max scroll* 意外提前?

Why setting scrollLeft and scrollTop on mousemove results in max scroll* unexpectedly early?

在回答another question on Whosebug, I posted what was supposed to be a simple demo showing how to achieve movement of a child <div> relative to the movement of the cursor within its parent <div>, by the manipulation of element.scrollTop and element.scrollLeft.

我的演示基本上按预期工作,演示了在 mousemove via event.pageX and event.pageY 上收集光标坐标的基础知识,做一些数学运算来计算较大的 .inner child 应该的比率移动,并应用 scroll*.

但是,我注意到当我将光标移动到 .outer parent 右下方 and/or 附近时,.inner child 会在我的光标到达边缘之前滚动到最大值。

我做了什么找到解决方案?
意识到 event.pageX 总是至少 1 并且绝不会比 width1 并且 event.pageY 永远不会超过 1少了parent<div>height,我加了一个基本的函数,把坐标范围从0扩展到全widthand/or height.
尽管该功能发挥了作用,但它并没有治愈过早的最大值 scroll*.

EDIT:我没有在 SO 片段之外测试这段代码,这些片段似乎以不同方式呈现 HTML,具体取决于它是被编辑、正常查看还是展开;有时该函数是 必需的 ,有时它的存在会导致负值。

scrollTop doesn't respond to negative values; instead, it sets itself back to 0.

If set to a value greater than the maximum available for the element, scrollTop settles itself to the maximum value.

scrollLeft也是如此。
所以这种不一致是无关紧要的;在我添加函数之前问题就很明显了。

还有什么?
我反复检查了数学,例如

考虑到这一点,正如我所说,我已经检查并再次检查,数学应该有效,但结果出乎意料。

这是代码,一些额外的位输出 innerHTML 中的一些数字(否则控制台会有点妨碍),以及我的问题将继续在它之下。额外的 <output> UI 不影响结果。

const divs = document.querySelectorAll( "div" ),
      outer_div = divs[ 0 ],
      outer_div_styles = window.getComputedStyle( outer_div ),
      inner_div_styles = window.getComputedStyle( divs[ 1 ] ),
      outer_div_width = parseInt( outer_div_styles.width ),
      outer_div_height = parseInt( outer_div_styles.height ),
      dimention_ratio = {
        x: parseInt( inner_div_styles.width ) / outer_div_width,
        y: parseInt( inner_div_styles.height ) / outer_div_height
      },
      half_odw = outer_div_width / 2,
      half_odh = outer_div_height / 2,
      expandCoords = function( e ) { // sometimes useful, never harmful
        var X = e.pageX,
            Y = e.pageY;
        if ( X < half_odw ) {
          X -= 1;
        } else if ( X > half_odw ) {
          X += 1;
        }
        if ( Y < half_odh ) {
          Y -= 1;
        } else if ( Y > half_odh ) {
          Y += 1;
        }
        return { x: X, y: Y };
      },
      // for demo convenience
      output = document.querySelector( "output" );
outer_div.addEventListener( "mousemove", function( evt ) {
  evt = expandCoords( evt );
  // for demo convenience
  output.innerHTML = "Coords: x:" + evt.x + ", y:" + evt.y + "<br>" +
                     "scrollLeft = " + ( evt.x * dimention_ratio.x ) + "<br>" +
                     "scrollTop = " + ( evt.y * dimention_ratio.y );
  outer_div.scrollLeft = evt.x * dimention_ratio.x;
  outer_div.scrollTop = evt.y * dimention_ratio.y;
}, false );
body {
  overflow: hidden;
  margin: 0;
}
.outer {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
.inner {
  width: 1000vw; /* 10 times the width of its parent */
  height: 500vh; /* 5 times the height of its parent */
  box-shadow: inset 0 0 20px 20px green; /* no border edge highlight */
  background: radial-gradient( white, black );
}

/* for demo convenience */
output {
  position: absolute;
  top: 10vh;
  left: 10vw;
  font-family: Consolas, monospace;
  background: white;
  padding: .2em .4em .3em;
  cursor: default;
}
<div class="outer"><div class="inner"></div><output></output></div>

如您所见,child <div> 的右侧和底部边缘在光标到达右侧 and/or 底部边缘之前很久就进入视野parent.

这是为什么,如何在动态应用程序中修复?

“动态应用程序” 我的意思是不对解决方案进行逐案硬编码。

注意:虽然(我知道)这段代码可以在很多方面优化,但它纯粹是为了演示,因此,优化不影响对特定问题的修复不感兴趣。

我想通了

scrollTopscrollLeft 是在各自的轴上滚动了多少像素的量度。
这相当于滚动了多少超出视图,所以总有剩余量无法滚动。
此金额等于 parent.

的相应度量
  • 如果 child <div> 的大小是 parent 的两倍,那么当在任一轴上滚动到最大值时,只会滚动一半。
  • 如果 child 是大小的三倍,在最大滚动时,将滚动三分之二。
  • 10 倍大小:9/10ths 滚动

在这个特定的应用程序中,光标坐标应乘以尺寸比率计算如下:

( width of the child minus the width of the parent ) divided by the width of the parent

( height of the child minus the height of the parent ) divided by the height of the parent

所以更正后的代码,处理任意比率:

const divs = document.querySelectorAll( "div" ),
      outer_div = divs[ 0 ],
      outer_div_styles = window.getComputedStyle( outer_div ),
      inner_div_styles = window.getComputedStyle( divs[ 1 ] ),
      outer_div_width = parseInt( outer_div_styles.width ),
      outer_div_height = parseInt( outer_div_styles.height ),
      dimention_ratio = {
        x: ( parseInt( inner_div_styles.width ) - outer_div_width ) / outer_div_width, // fixed
        y: ( parseInt( inner_div_styles.height ) - outer_div_height ) / outer_div_height // fixed
      },
      half_odw = outer_div_width / 2,
      half_odh = outer_div_height / 2,
      expandCoords = function( e ) {
        var X = e.pageX,
            Y = e.pageY;
        if ( X < half_odw ) {
          X -= 1;
        } else if ( X > half_odw ) {
          X += 1;
        }
        if ( Y < half_odh ) {
          Y -= 1;
        } else if ( Y > half_odh ) {
          Y += 1;
        }
        return { x: X, y: Y };
      };
outer_div.addEventListener( "mousemove", function( evt ) {
  evt = expandCoords( evt );
  outer_div.scrollLeft = evt.x * dimention_ratio.x;
  outer_div.scrollTop = evt.y * dimention_ratio.y;
}, false );
body {
  overflow: hidden;
  margin: 0;
}
.outer {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
.inner {
  width: 1234vw; /* 12.34 times the width of its parent */
  height: 567vh; /* 5.67 times the height of its parent */
  box-shadow: inset 0 0 20px 20px green; /* no border edge highlight */
  background: radial-gradient( white, black );
}
<div class="outer"><div class="inner"></div></div>