Working With an HTML Element's Position Onscreen in Vanilla JavaScript
December 13, 2020
HTML elements move around the screen because of the way we scroll documents on the web. Furthermore, embracing responsiveness with regards to screen size means that elements might change size and position depending on context. These are a couple reasons you might want to look at an HTML element's position onscreen using JavaScript. Below we'll discuss the basics of how we can work with element position onscreen. Though these techniques, like anything on the web, might be implemented differently from browser to browser, we can use them to begin working with an element's position onscreen.
The Viewport
For starters, we need to understand what the viewport is. The viewport is the part of a web page that's visible onscreen. The beginning or the end of the document might have been scrolled offscreen, but the viewport shows us the part of the document that we're currently scrolled to.
We might be interested in grabbing references to the viewport's height and weight in our JavaScript. A good way to do this is accessing window.innerHeight
and window.innerWidth
. These properties provide the viewport dimensions in pixels.
// Grab pixel dimensions of the viewport
var viewportHeight = window.innerHeight;
var viewportWidth = window.innerWidth;
Of course, the viewport's dimensions can change quite easily! For example, users might resize the browser window or change the orientation of their phone from portrait to landscape. You may wish to set up a pair of event listeners to keep your viewport dimension variables current.
// Wrap viewport check in its own function
var checkViewportSize = () => {
viewportHeight = window.innerHeight;
viewportWidth = window.innerWidth;
console.log(viewportHeight, 'h x', viewportWidth, 'w');
}
// Now we'll assign this to events
// Set event listener for window resize
window.addEventListener('resize', () => {
checkViewportSize();
});
// Set event listener for device orientation change
window.addEventListener('orientationchange', () => {
checkViewportSize();
});
CSS Note! |
---|
The CSS length units vh and vw refer to the viewport's height and width respectively. You can use the viewport's size by employing these units in your stylesheet! 1vh is 1% of the height of the viewport; 80vw is 80% of the width of the viewport. |
Finding an Element's Position in the Viewport
Knowing the dimensions of the viewport is more useful once we start to check an HTML element's position relative to the viewport. For this we can use Element.getBoundingClientRect()
.
Getting the Position Data
Calling getBoundingClientRect()
on an element will return data about its size and position relative to the viewport. The data is wrapped in a DOMRect object that provides the element's x
and y
positions in the viewport, and its width
and height
. It also provides measurements of the top
side's distance from the top of the viewport, the bottom
side's distance from the top of the viewport, the left
side's distance from the left side of the viewport, and the right
side's distance from the left side of the viewport. Thus, the x
and left
properties of the DOMRect will always be the same, and the y
and top
properties should always be the same as well.
To test this out, let's create an HTML element and give it the ID target
.
<p id="target">Target element</p>
Now we can grab this element in our JavaScript and check its position onscreen.
// Grab the target element
var element = document.getElementById('target');
// Get a rect object
var rect = element.getBoundingClientRect();
// The rect has all the data we want
console.log(rect);
By logging the DOMRect that we get from Element.getBoundingClientRect()
, we can see all of the target element's size and viewport-relative position data.
Check If an Element is Visible in the Viewport
To determine whether or not an element is currently visible within the viewport, we might want to write a little helper function.
var isInViewport = (element) => {
var rect = element.getBoundingClientRect();
var position = rect.top/viewportHeight;
if (position >= 0 && position <= 1) {
return true;
}
else {
return false;
}
}
This function checks whether the element passed into it is within the viewport and returns either true or false. We can also get more granular using the same approach and logic.
Check How Far an Element is Scrolled in the Viewport
Divide the top
property by the viewportHeight
to determine what percentage of the screen the element is scrolled from the top. Zero would put our element at the very top of the viewport. 1 or 100% would put our element at the very bottom of the page. A negative value represents scrolling the element up beyond the top of the viewport, and a number larger than 1 would be scrolled beyond the bottom of the viewport, that is, more than 100% of the viewport height.
// Function to check target element's position
var checkTargetPosition = () => {
// get bounding client rect from element
var rect = element.getBoundingClientRect();
// grab measurements and percentage conversion
var fromTop = rect.top;
var fraction = rect.top/viewportHeight;
var percentage = fraction * 100;
console.log('target scroll:', fromTop, 'px from top.', fraction, '/', percentage, '%');
}
// Listen for scroll event and check position
window.addEventListener('scroll', () => {
checkTargetPosition();
});
Both the checkTargetPosition()
and the isInViewport()
functions above use the same approach of dividing the top
property by the viewport height and apply the same logic in the interpretation of the results.
While this exercise illustrates how to check element position, firing the checkTargetPosition()
function constantly during scrolling is performance-heavy, and probably doesn't look too smooth. We might want to use an approach like debounce to limit how often we fire that function.
Try It Out
While the functions above illustrate checking the dimensions of the viewport and the usage of Element.getBoundingClientRect()
, we need a beefier sample to really demonstrate how this stuff works: we need a page with enough content that we can actually scroll our target element around.
Try this--HTML boilerplate with styles for readability and scrollability, and long passages of lorem ipsum on either side of our target element:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html {
font-size: 200%;
}
#target {
background: #ccc;
}
</style>
</head>
<body>
<p>Etiam porta sem malesuada magna mollis euismod. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p><p>Etiam porta sem malesuada magna mollis euismod. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p><p>Etiam porta sem malesuada magna mollis euismod. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
<p id="target">Target element</p>
<p>Etiam porta sem malesuada magna mollis euismod. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p><p>Etiam porta sem malesuada magna mollis euismod. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p><p>Etiam porta sem malesuada magna mollis euismod. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
<script>
// We need to reassemble our JavaScript here
</script>
</body>
We need to be sure to keep all our JavaScript on the page.
One thing you may notice as you play around with this newly-scrollable test page is that the top
parameter that we're working with to see how far an element is scrolled might not be as useful as a parameter that provided the location of the vertical center of the element in question. To get that position, we would need to get half the height of the element itself and add that to the top
value.
Put It To Good Use
Here's an idea to try out: can you make the elements on a page change CSS class as they scroll up the screen? To get started, you might create an array of objects, each detailing a scroll threshold where class changes occur.
var thresholds = [
{ className: 'class1', position: 50 },
{ className: 'class2', position: 150 },
{ className: 'class3', position: 250 }
];
Use more JavaScript to setup a function that fires on scroll and checks an object's onscreen position, then compares that to the positions in the array of thresholds and applies the appropriate class.
Of course, there are JavaScript libraries that wrap up scrolling/viewport behaviors but I haven't tried them. After all, the idea of this series is to demonstrate the power of vanilla JavaScript. One final caveat before you go out into the world to use these techniques--different situations may call for a different approach. Try things out and build up a sense for when you'll need certain JavaScript tools! And always remember the importance of cross-browser testing.