Drawing HTML Elements upwards/downwards dynamically in the Screen

Originally published at labs.thisdot.co

Jul 12, 2021 · 4 minutes read · [JavaScript] [TypeScript]

A couple of weeks ago, I start writing a custom component that allows rendering a list of other components dynamically. One of its features shows the list up or down, according to the space available in the viewport (or the screen).

A solution to this type of problem may require the use of the web APIs available through the Window interface.

The Web APIs

API comes from Application Programming Interface, and according to MDN:

Application Programming Interfaces (APIs) are constructs made available in programming languages to allow developers to create complex functionality more easily. They abstract more complex code away from you, providing some easier syntax to use in its place.

In the same way, the Web currently comes with a large number of Web APIs we can use through JavaScript(or TypeScript). You can find more detailed information about them here.

The Window Interface

The Window interface represents a window containing a DOM document. In the practical world, we usually use the global window variable to get access to it.

For example, open the browser’s console, and type the following command there:

window.document

Once you run the previous command, you should get a reference to the current document, which is contained by the window object. In Google Chrome, you’ll see a selection effect applied over the whole page. Give it a try!

Let’s try with another example. Type the next command in your browser’s console:

window.history

The output will be something like this:

window.history command return a History object

As you can see, the result is a History object. This is a read-only reference, and can be used to change the current browser history as the next example shows:

const history = window.history;
history.back();

All the above examples were about access to properties. However, the window object also provides useful methods. For example:

window.open();

window.find('pattern');

The first method call will open a new window browser, and the second one will search the given string within the current view.

Using the APIs

First, let’s define the HTML template of the list to be rendered dynamically:

<div class="list">
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
    <li>Item 4</li>
    <li>Item 5</li>
    <li>Item 6</li>
    <li>Item 7</li>
  </ul>
</div>

Now, let’s apply some styles so that it stands out from the container.

.list {
  display: none;
  border: 1px solid #00458b;
  min-width: 200px;
  position: absolute;
  background: #eee;
}

As you may note, the list won’t be rendered by default(display: none), and we’re going to set its position from the code.

Listening to a Mouse Click Event

Let’s create a TypeScript file (index.ts) so that we can start using the Web APIs, and the window object.

As the next step, we’ll need to “catch” the mouse click event globally. Then, the window reference can be helpful for that:

// index.ts

window.addEventListener('click', (event: MouseEvent) => {
  console.log('click', event.x, event.y);
});

Once you run the previous code snippet, the browser’s console will print the click event coordinates.

Getting the List from the DOM

The HTML List we created can be accessed using the following JavaScript code:

const listElement = window.document.querySelector('.list') as HTMLDivElement;
  • The window.document will return a reference to the Document object contained in the window.
  • The querySelector method will return the first element that matches with the selector(.list).
  • Since TypeScript is being used, we can use the as syntax to perform a type assertion.

Rendering the List Dynamically

Before rendering the list dynamically, we’ll need to perform some calculations based on the list size, and the container.

Let’s get the list size using the getBoundingClientRect() method:

const boundingRect = listElement.getBoundingClientRect() as DOMRect;

This method returns a DOMRect object, which contains the coordinates and the size of the element with other properties.

const viewportHeight = window.innerHeight;

The window.innerHeight property returns the interior height of the window, in pixels.

Let’s read the mouse click event coordinates too:

let { x, y } = event;

In this case, we’ll do a calculation for the y-axis coordinate:

if (event.y + boundingRect.height >= viewportHeight) {
    console.log('render upwards');
    y = y - boundingRect.height;
} else {
  console.log('render downwards');
}

The above code snippet will make sure the list can fit below. Otherwise, it will render it upwards the click position.

As a final step, we’ll need to update the visibility of the list, and update the position of if after every click event:

listElement.style.display = 'block';
listElement.style.left = `${x}px`;
listElement.style.top = `${y}px`;

Live Demo

Want to play around with this code? Just open the embedded Stackblitz editor:

Conclusion

In this article, I described a useful technique to rely on existing Web APIs to solve a problem. Keep in mind this example is a proof of concept only and you may need to perform additional calculations or accessing different window properties.


Feel free to reach out on Twitter if you have any questions. Follow me on GitHub to see more about my work.

tweet Share