关于javascript:为什么在mousemove上设置scrollLeft和scrollTop会导致最大滚动*意外地提前?

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

在回答有关StackOverflow的另一个问题时,我发布了一个简单的演示,该演示演示如何通过操纵element.scrollTopelement.scrollLeft

我的演示基本上按预期工作,演示了通过event.pageXevent.pageYmousemove上收集光标坐标的基本知识,做了一些数学运算来计算应移动较大的.inner子项的比率,并应用scroll*

但是,我注意到当将光标移到.outer父级的底部和/或右侧附近时,.inner子级将在光标到达边缘之前滚动到其最大值。铅>

我为寻找解决方案做了什么?
意识到event.pageX始终至少为1,并且永远不会比width小,并且event.pageY永远不会比父height小。 >,我添加了基本功能以将坐标范围从0扩展到完整的width和/或height
尽管该功能可以发挥作用,但不能解决最大过早的scroll*

编辑:我没有在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也是一样。
因此,这种不一致是无关紧要的。在添加功能之前,问题很明显。

还有什么?
我已经反复检查过数学,例如

  • 假设我们有一个父100px来度量100px
  • 还有一个子项300px来度量300px(两个轴上父级的3倍)
  • 如果光标在坐标上。 { x: 90, y: 90 }
  • scrollTop应设置为90 * 3 = 270
  • 并且scrollLeft应该设置为90 * 3 = 270
  • 因此,子级的底部或右侧边缘不应与父级的底部或右侧边缘对齐。

考虑到这一点,正如我所说的,我已经检查了一次并再次检查了一下,数学应该可以工作,但是结果出乎意料。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 +"" +
                    "scrollLeft =" + ( evt.x * dimention_ratio.x ) +"" +
                    "scrollTop =" + ( evt.y * dimention_ratio.y );
  outer_div.scrollLeft = evt.x * dimention_ratio.x;
  outer_div.scrollTop = evt.y * dimention_ratio.y;
}, false );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}
1
<output></output>

您应该看到,子的右边缘和底边缘早在光标到达父对象的右边缘和/或底边缘之前就已可见。

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

"动态应用"是指无需根据具体情况对解决方案进行硬编码。

注意:尽管(我知道)此代码可以通过许多方式进行优化,但它仅用于演示,因此,不影响不解决特定问题的优化的优化就没有意义了。


我想到了

scrollTopscrollLeft是在其各自的轴上滚动多少像素的度量。
这等于滚动了多少视线,因此始终有无法滚动的剩余量。
此数量等于父项的相应度量。

  • 如果子代的大小是父代大小的两倍,则在任一轴上滚动到其最大值时,将只滚动一半。
  • 如果孩子的大小是孩子的三倍,则最大滚动时,将滚动三分之二。
  • 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

因此,更正后的代码可以处理任意比率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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 );
}
1