Working With an HTML Element's Position Onscreen in Vanilla JavaScript
Tags: javascript, html, beginners, webdev
Published: 2020-12-13 16:55:17

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.

```javascript
// 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.

```javascript
// 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`.

```html
<p id="target">Target element</p>
```

Now we can grab this element in our JavaScript and check its position onscreen.

```javascript
// 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.

```javascript
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.

```javascript
// 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](https://dev.to/sprite421/borrowing-a-javascript-debounce-3m59) 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:

```html
<!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](https://dev.to/sprite421/checking-the-dimensions-of-an-html-element-in-vanilla-javascript-3kpn) 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.

```javascript
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.