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.
scrollTopdoesn't respond to negative values; instead, it sets itself back to0.If set to a value greater than the maximum available for the element,
scrollTopsettles 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>measuring100pxby100px - And a child
<div>measuring300pxby300px( 3 times the parent on both axes) - If the cursor is at coord.
{ x: 90, y: 90 } - The
scrollTopshould be set to90 * 3=270 - And
scrollLeftshould 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.