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:
useCapture
argumentmentioned previouslytrue
, indicates that the event should only run once on the targeted element and then be removedtrue
, indicates that the function will never call preventDefault()
, even if it’s included in the function bodyThe 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.
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:
scrollTo()
is across the board, but not all browsers support the options
objectscroll()
and scrollBy()
methodsIn 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.
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
.
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:
wholeText
on one of the text nodes, rather than the element (hence el.childNodes[0]
in the code; el.childNodes[1]
would also work)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.
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 calledafterbegin
: Inserted inside the element, before its first childbeforeend
: Inserted inside the element, after its last childafterend
: Inserted after the elementAs 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.
But there’s more you can do with that event
object. In fact, when certain events are used (e.g. click
, dbclick
, mouseup
, mousedown
), 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:
dbclick
, which is always two. Firefox only allows up to three clicks then the count starts againblur
and focus
to demonstrate that these don’t qualify and will always return 0 (i.e. no clicks)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 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 scrollHeight
to 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.
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.
Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third-party services are successful, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.