Louis Lazaris Louis Lazaris is a front-end developer, author, writer, and speaker who has been involved in the web development industry since 2000. He blogs about front-end code at Impressive Webs (https://www.impressivewebs.com/) and runs two tech newsletters, Web Tools Weekly (https://webtoolsweekly.com/) and Tech Productivity (https://techproductivity.co/).

8 DOM features you didn’t know existed

10 min read 2961

With so much focus on tooling lately, it’s nice to take a break from all of the React and npm-install-everything posts and take a closer look at some pure DOM and Web API features that work in modern browsers with no dependencies.

This post will consider eight lesser-known DOM features that have strong browser support. To help explain how each one works, I’m going to include lots of interactive demos for you to try out the code for yourself.

These methods and properties don’t have a steep learning curve and will work well alongside whatever toolset you happen to be bringing to your projects.

You’ve most certainly used addEventListener() to deal with attaching events to elements in a web document. Usually, an addEventListener() call looks something like this:

element.addEventListener('click', doSomething, false);

The first argument is the event I’m listening for. The second argument is a callback function that will execute when the event occurs. The third argument is a Boolean called useCapture to indicate if you want to use event bubbling or capturing.

Those are pretty well known (especially the first two). But maybe you didn’t know that addEventListener() also accepts an argument that replaces the final Boolean. This new argument is an options object that looks like this:

element.addEventListener('click', doSomething, {
  capture: false,
  once: true,
  passive: false
});

Notice the syntax allows for three different properties to be defined. Here’s a quick rundown of what each one means:

  • capture — A Boolean that’s the same as the useCapture argumentmentioned previously
  • once — A Boolean that, if set to true, indicates that the event should only run once on the targeted element and then be removed
  • passive — A final Boolean that, if set to true, indicates that the function will never call preventDefault(), even if it’s included in the function body

The most interesting of those three is the once option. This will definitely come in handy in many circumstances and will keep you from having to use removeEventListener() or use some other complex technique to force a single event trigger. If you’ve used jQuery, you might be familiar with a similar feature in that library, the .one() method.

You can try out some code that uses the options object in the following CodePen:

See the Pen
Using addEventListener() with an `options` Object as Third Parameter
by Louis Lazaris (@impressivewebs)
on CodePen.

Notice the button on the demo page will append the text only once. If you change the once value to false, then click the button multiple times, the text will be appended on each button click.

Browser support for the options object is excellent: all browsers support it except for IE11 and earlier, so it’s pretty safe to use if you’re not worried about pre-Edge Microsoft browsers.

The scrollTo() method for smooth scrolling in windows or elements

Smooth scrolling has always been a need. It’s jarring when a local page link jumps immediately to a specified place (if you blink, you might even miss the jump). Smooth scrolling is one of those things that not only looks good but improves a page’s UX.

While this has been done in the past using jQuery plugins, it’s now possible with just one line of JavaScript using the window.scrollTo() method.

The scrollTo() method is applied to the Window object to tell the browser to scroll to a specified place on the page. For example, here’s an example with the simplest syntax:

window.scrollTo(0, 1000);

This will scroll the window 0px to the right (representing the x coordinate, or horizontal scrolling) and 1000px down the page (the vertical, which is usually what you want). But in that case, the scrolling will not be a smooth animated effect; the page will scroll abruptly, the same as if you used a local link targeted at a specified hash URL.

Sometimes that’s what you want. But in order to get smooth scrolling, you have to incorporate the lesser-known ScrollToOptions object, like this:

window.scrollTo({
  top: 0,
  left: 1000,
  behavior: 'smooth'
});

This code is equivalent to the previous example, but with the addition of the smooth value for the behavior property inside the options object.

See the Pen
Using Options with window.ScrollTo()
by Louis Lazaris (@impressivewebs)
on CodePen.

Try entering a number into the box (preferably a large one like 4000) and change the “behavior” select box to use either smooth or auto (which are the only two options for the behavior property).

Some notes on this feature:

  • Basic support for scrollTo() is across the board, but not all browsers support the options object
  • This method will also work when applied to an element instead of the window
  • The options are also applicable to the scroll() and scrollBy() methods

setTimeout() and setInterval() with optional arguments

In many cases, doing timing-based animations using window.setTimeout()and window.setInterval() has now been replaced by the more performance-friendly window.requestAnimationFrame(). But there are situations where setTimeout()or setInterval()are the right choice, so it’s good to know about a little-known feature of these methods.

Normally you’ll see these either of these methods with syntax like this:

let timer = window.setInterval(doSomething, 3000);
function doSomething () {
  // Something happens here...
}

Here the setInterval() call passes in two arguments: the callback function and the time interval. With setTimeout()this would run once, whereas in this case it runs indefinitely until I call window.clearTimeout() while passing in the timer variable.

Simple enough. But what if I wanted my callback function to take arguments? Well, a more recent addition to these timer methods allows the following:

let timer = window.setInterval(doSomething, 3000, 10, 20);
function doSomething (a, b) {
  // Something happens here…
}

Notice I’ve added two more arguments to my setInterval() call. My doSomething() function then accepts those as parameters and can manipulate them as needed.

Here’s a CodePen demo that demonstrates how this works using setTimeout():

See the Pen
Optional Parameters with window.setTimeout()
by Louis Lazaris (@impressivewebs)
on CodePen.

When you click the button, a calculation will take place with two passed in values. The values can be changed via the number inputs on the page.

As for browser support, there seems to be inconsistent information on this, but it looks like the optional parameters feature is supported in just about all in-use browsers, including back to IE10.

The defaultChecked property for radio buttons and checkboxes

As you probably know, for radio buttons and checkboxes, if you want to get or set the checked attribute, you can use the checked property, like this (assuming radioButton is a reference to a specific form input):

console.log(radioButton.checked); // true
radioButton.checked = false;
console.log(radioButton.checked); // false

But there’s also a property called defaultChecked, which can be applied to a radio button or checkbox group to find out which one in the group was initially set to checked.

Here’s some example HTML:

<form id="form">
  <input type="radio" value="one" name="setOne"> One
  <input type="radio" value="two" name="setOne" checked> Two<br />
  <input type="radio" value="three" name="setOne"> Three
</form>

With that, even after the checked radio button has been changed, I can loop through the inputs and find out which one was checked initially, like this:

for (i of myForm.setOne) {
  if (i.defaultChecked === true) {
    console.log(‘i.value’);
  }
}

Below is a CodePen demo that will display either the currently checked radio button or the default checked one, depending on which button you use:

See the Pen
defaultChecked on Radio Buttons
by Louis Lazaris (@impressivewebs)
on CodePen.

The defaultChecked option in that example will always be the “Two” radio button. As mentioned, this can also be done with checkbox groups. Try changing the default checked option in the HTML then try the button again.

Here’s another demo that does the same with a group of checkboxes:

See the Pen
defaultChecked on Checkboxes
by Louis Lazaris (@impressivewebs)
on CodePen.

In this case, you’ll notice that two of the checkboxes are checked by default, so those will both return true when queried using defaultChecked.

Manipulating text nodes with normalize() and wholeText

Text nodes in an HTML document can be finicky, especially when the nodes are inserted or created dynamically. For example, if I have the following HTML:

<p id="el">This is the initial text.</p>

I can then add a text node to that paragraph element:

let el = document.getElementById('el');
el.appendChild(document.createTextNode(' Some more text.'));
console.log(el.childNodes.length); // 2

Notice that after the text node is appended, I log the length of the child nodes inside the paragraph, and it says there are two nodes. Those nodes are a single string of text, but because the text is appended dynamically, they’re treated as separate nodes.

In certain cases, it would be more helpful if the text was treated like a single text node, which makes the text easier to manipulate. This is where normalize() and wholeText() come in.

The normalize() method can be used to merge the separate text nodes:

el.normalize();
console.log(el.childNodes.length); // 1

Calling normalize() on an element will merge any adjacent text nodes inside that element. If there happens to be some HTML interspersed among adjacent text nodes, the HTML will stay as it is while all adjacent text nodes will be merged.

But if for some reason I want to keep the text nodes separate, but I still want the ability to grab the text as a single unit, then that’s where wholeText is useful. So instead of calling normalize(), I could do this on the adjacent text nodes:

console.log(el.childNodes[0].wholeText);
// This is the initial text. Some more text.
console.log(el.childNodes.length); // 2

As long as I haven’t called normalize(), the length of the text nodes will stay at 2 and I can log the entirety of the text using wholeText. But note a few things:

  • I have to call wholeText on one of the text nodes, rather than the element (hence el.childNodes[0] in the code; el.childNodes[1] would also work)
  • The text nodes have to be adjacent, with no HTML separating them

You can see both features, along with the splitText() method, in use in this CodePen demo. Open the CodePen console or your browser’s developer tools console to see the logs produced.

insertAdjacentElement() and insertAdjacentText()

Many of you will probably be familiar with the insertAdjacentHTML() method that allows you to easily add a string of text or HTML to a specific place in the page in relation to other elements.

But maybe you weren’t aware that the spec also includes two related methods that work in a similar way: insertAdjacentElement()and insertAdjacentText().

One of the flaws of insertAdjacentHTML() is the fact that the inserted content has to be in the form of a string. So if you include HTML, it has to be declared like this:

el.insertAdjacentHTML('beforebegin', '<p><b>Some example</b> text goes here.</p>');

However, with insertAdjacentElement(), the second argument can be an element reference:

let el = document.getElementById('example'),
addEl = document.getElementById('other');
el.insertAdjacentElement('beforebegin', addEl);

What’s interesting about this method is that this will not only add the referenced element to the specified position, but it will also remove the element from its original place in the document. So this is an easy way to transfer an element from one location in the DOM to another.

Here’s a CodePen demo that uses insertAdjacentElement(). The button click effectively “moves” the targeted element:

See the Pen
Using insertAdjacentElement() to Change an Element’s Location
by Louis Lazaris (@impressivewebs)
on CodePen.

The insertAdjacentText() method works similarly, but the string of text provided will be inserted exclusively as text, even if it contains HTML. Note the following demo:

See the Pen
Using insertAdjacentText() with HTML Tags
by Louis Lazaris (@impressivewebs)
on CodePen.

You can add your own text to the input field, and then use the button to add it to the document. Notice any special characters (like HTML tags) will be inserted as HTML entities, differentiating how this method behaves compared to insertAdjacentHTML().

All three methods ( insertAdjacentHTML()insertAdjacentElement(), and insertAdjacentText()) take the same values for the first argument. The arguments are:

  • beforebegin: Inserted before the element on which the method is called
  • afterbegin: Inserted inside the element, before its first child
  • beforeend: Inserted inside the element, after its last child
  • afterend: Inserted after the element

The event.detail Property

As already discussed, events are attached to elements on a web page using the familiar addEventListener() method. For example:

btn.addEventListener('click', function () {
  // do something here...
}, false);

When using addEventListener(), you may have had the necessity to prevent a default browser behavior inside the function call. For example, maybe you want to intercept clicks on <a> elements and handle the clicks with JavaScript. You would do this:

btn.addEventListener('click', function (e) {
  // do something here...
  e.preventDefault();
}, false);

This uses preventDefault(), which is the modern equivalent of the old-school return false statement. This requires that you pass the event object into the function, because the preventDefault() method is called on that object.

Sick of debugging web apps? Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket pairs session replay with technical telemetry to quickly understand what went wrong.

Get a Free Trial of LogRocket

or

But there’s more you can do with that event object. In fact, when certain events are used (e.g. clickdbclickmouseupmousedown), these expose something called a UIEvent interface. As MDN points out, a lot of the features on this interface are deprecated or not standardized. But the most interesting and useful one is the detail property, which is part of the official spec.

Here’s how it looks in the same event listener example:

btn.addEventListener('click', function (e) {
  // do something here...
  console.log(e.detail);
}, false);

I’ve set up a CodePen demo that demonstrates the results using a number of different events:

See the Pen
Using the event.detail Property
by Louis Lazaris (@impressivewebs)
on CodePen.

Each of the buttons in the demo will respond in the way the button text describes and a message showing the current click count will be displayed. Some things to note:

  • WebKit browsers allow an unlimited click count, except on dbclick, which is always two. Firefox only allows up to three clicks then the count starts again
  • I’ve included blur and focus to demonstrate that these don’t qualify and will always return 0 (i.e. no clicks)
  • Older browsers like IE11 have very inconsistent behavior

Notice the demo includes a nice use case for this — the ability to mimic a triple-click event:

btnT.addEventListener('click', function (e) {
  if (e.detail === 3) {
    trpl.value = 'Triple Click Successful!';
  }
}, false);

If all browsers counted past three clicks, then you could also detect a higher click count, but I think for most purposes a triple-click event would suffice.

The scrollHeight and scrollWidth properties

The scrollHeight and scrollWidth properties might sound familiar to you because you might be confusing them with other width- and height-related DOM features. For example, the offsetWidth and offsetHeight properties will return the height or width of an element without factoring in overflow.

For example, note the following demo:

See the Pen
offsetHeight Doesn’t Count Past CSS Overflow
by Louis Lazaris (@impressivewebs)
on CodePen.

The columns in the demo have the same content. The column on the left has overflow set to auto while the column on the right has overflow set to hidden. The offsetHeight property returns the same value for each because it doesn’t factor in the scrollable or hidden areas; it only measures the actual height of the element, which includes any vertical padding and borders.

On the other hand, the aptly named scrollHeight property will calculate the full height of the element, including the scrollable (or hidden) area:

See the Pen
scrollHeight Measures an Element’s Full Scrollable Area
by Louis Lazaris (@impressivewebs)
on CodePen.

The demo above is the same as the previous one except it uses scrollHeightto get the height of each column. Notice again the value is the same for both columns. But this time it’s a much higher number because the overflow area is also counted as part of the height.

The examples above focused on element height, which is the most common use case, but you can also use offsetWidth and scrollWidth, which would be applied the same way in relation to horizontal scrolling.

Conclusion

That’s it for this list of DOM features, these are probably some of the most interesting features I’ve come across over the last couple years so I hope at least one of these is something you’ll be able to use in a project in the near future.

Let me know in the comments if you’ve used one of these before or if you can think of some interesting use cases for any of them.

Plug: LogRocket, a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

Try it for free.

 

Louis Lazaris Louis Lazaris is a front-end developer, author, writer, and speaker who has been involved in the web development industry since 2000. He blogs about front-end code at Impressive Webs (https://www.impressivewebs.com/) and runs two tech newsletters, Web Tools Weekly (https://webtoolsweekly.com/) and Tech Productivity (https://techproductivity.co/).

Leave a Reply