
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 callpreventDefault()
, 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:
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.
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 theoptions
object - This method will also work when applied to an element instead of the window
- The options are also applicable to the
scroll()
andscrollBy()
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()
:
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:
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:
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 (henceel.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:
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:
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 element
The event.detail Property
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:
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
andfocus
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:
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:
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.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.
Comments
Post a Comment