In answering another question on StackOverflow, 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
.
My demo basically works as expected, demonstrating the basics of gathering the cursor coordinates on mousemove
via event.pageX
and event.pageY
, doing a little math to calculate the ratio by which the larger .inner
child should be moved, and applying the scroll*
.
However, I noticed that when moving my cursor near to the bottom and/or right of the .outer
parent, the .inner
child would be scrolled to its maximum ahead of my cursor reaching the edge(s).
What have I done to find a solution?
Realising that event.pageX
would always be at least 1
and never more than 1
less than the width
and that event.pageY
would never be more than 1
less of the parent <div>
's height
, I added a rudimentary function to expand the coordinate range from 0
to the full width
and/or height
.
Although the function does its job, it didn't cure the premature maximum scroll*
.
EDIT: I had not tested this code outside SO snippets which appear to present the HTML differently depending on if it's being edited, viewed normally or expanded; Sometimes the function is required, and sometimes it's presence results in negative values.
scrollTop
doesn't respond to negative values; instead, it sets itself back to0
.If set to a value greater than the maximum available for the element,
scrollTop
settles itself to the maximum value.
The same is true for scrollLeft
.
So this inconsistency isn't relevant; the problem was evident before I added the function.
What else?
I have repeatedly checked the math and, e.g.
- Say we have a parent
<div>
measuring100px
by100px
- And a child
<div>
measuring300px
by300px
( 3 times the parent on both axes) - If the cursor is at coord.
{ x: 90, y: 90 }
- The
scrollTop
should be set to90 * 3
=270
- And
scrollLeft
should be set to90 * 3
=270
- So the child
<div>
's bottom or right edges should not be aligned with those of the parent.
With that in mind, as I say, I have checked and checked again, and the math should work, but the result is unexpected.
Here's the code, with some extra bits outputting some of the the numbers in innerHTML
(the console gets in the way a bit otherwise), and my question will continue under it. The extra <output>
UI doesn't affect the result.
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>
As you should see, the right and bottom edge(s) of the child <div>
come into view long before the cursor reaches the right and/or bottom edge(s) of the parent.
Why is this, and how can it be fixed in a dynamic application?
By "dynamic application" I mean without hard coding the solution on a case by case basis.
NOTE: Although (I know) this code can be optimized in many ways, it is purely for demonstration and thus, optimisations that do not affect a fix for the specific problem are not of interest.