JavaScript and CSS improves on most sunny days. They add support for features that were previously done by libraries, sometimes even mimicking the API. And sometimes there was a native feature all along that has slipped past many of us. This page aims to provide a collection of things like that. Not a complete reference of everything that you can do, but a list of alternative ways that are sometimes overlooked.
Beware that these examples include code that might be unsupported in some browsers with IE and Safari being the most often victims. Some of the suggestions might be found controversial, but that doesn’t mean they should be hidden. Learn all the possibilities and choose according to circumstances.
The Selectors API let’s you select the elements just like in CSS and jQuery.
const element = document.querySelector('p.charming') // returns first match
const list = document.querySelectorAll('p.strange') // return NodeList
const element = $('p.charming').first()
const list = $('p.charming')
Note: the jQuery examples end up with a jQuery object not a raw element or NodeList so they are technically not equivalent. But it seemed silly to add [0]
as that’s not used if you actually work with jQuery.
Some people like to alias these to $('p.charming')
and $$('p.strange')
, but it might be mistaken for jQuery.
Remember that NodeList has a forEach
method and the length
property. You can loop over it however you like without creating an array or retrieving Array.prototype.forEach
.
The same API works for selecting inside an element.
const descendantElement = element.querySelector('p.charming')
const list = element.querySelectorAll('p.strange')
const children = element.children // not new, included for completeness like .parentNode later on
// to select among direct descendants use :scope pseudo-class that refers to element
const selectedChildren = element.querySelectorAll(':scope > p')
const childElement = $(element).find('p.charming').first()
const list = $(element).find('p.strange')
const children = $(element).children()
const selectedChildren = $(element).children('p')
const parent = element.parentNode // there is also .parentElement if you want to get confused
// go up the tree until something matches
const ancestor = element.closest('div.container')
const parent = $(element).parent()
const ancestor = $(element).closest('div.container')
Remember that these still work and are faster than anything else.
document.getElementById('higgs')
document.getElementsByClassName('top')
document.getElementsByTagName ('input')
document.getElementByName('attachments[]') // let me know if you have ever used this
// or search descendants in an element
element.getElementsByClassName('bottom')
element.getElementsByTagName ('label')
element.getElementByName('attachments[]')
// move (or insert if it's a new node)
parent.append(child)
parent.append(childA, childB, 'just a text string', childC)
parent.prepend(childX, childY)
sibling.after(anotherSibling)
sibling.after(aSx, aSy)
sibling.before(anotherSibling)
// remove
element.remove()
$(parent).append(child) // or $(child).appendTo(parent)
$(parent).append(childA, childB, 'just a text string', childC)
$(parent).prepend(childX, childY) // $([childX,childY]).prependTo(parent)
$(sibling).after(anotherSibling) // or $(anotherSibling).insertAfter(sibling)
$(sibling).after(aSx, aSy) // or $([aSx, aSy]).insertBefore(sibling)
$(sibling).before(anotherSibling) // or $(anotherSibling).insertBefore(sibling)
$(element).remove()
parent.appendChild(child) // can't insert many at once
parent.insertBefore(child, null) // yup, this is how you prepended
parent.insertAfter(newSibling, sibling)
parent.insertBefore(newSibling, sibling)
element.parentNode.removeChild(element)
element.classList.add('strange') // or element.classList.toggle('strange', true)
element.classList.add('stranger', 'charming')
// when you're tired of spelling element.classList
const cl = element.classList
cl.remove('stranger') // or cl.toggle('stranger', false)
cl.toggle('charming')
cl.replace('strange', 'strangest')
const hasClass = element.classList.contains('charming')
$(element).addClass('strange') // or $(element).toggleClass('strange', true)
$(element).addClass('stranger charming')
// when you're tired of spelling $(element)
const el = $(element)
el.removeClass('stranger') // or el.toggleClass('stranger', false)
el.toggleClass('charming')
el.switchClass('strange', 'strangest')
const hasClass = el.hasClass('charming')
Note: the toggle with a boolean value is used:
// to simplify this
if (condition)
element.classList.add('strange')
else
element.classList.remove('strange')
// into this
element.classList.toggle('strange', condition)
element.dataset.myProperty = '123' // sets data-my-property="123" on the element
const someData = element.dataset.stuff // retrieves value of data-stuff attribute
GETting is easy these days.
fetch(url).then(responseCallback)
// often you unpack the response immediatelly
fetch(url)
.then(response => response.json())
.then(callBackDealingWithTheJsonData)
You can also do more complicated things with the Fetch API. But maybe that’s where you’d want a library…
If you are stuck on jQuery just for the convenience, remember that a lot of classic JS methods and properties are simple and usable like element.textContent
, element.innerHTML
, element.nextElementSibling
, element.style
, getComputedStyle(element)
, element.getAttribute('some-attr')
, …
You can read about a lot of that on YMNNJ.
You can hide depending on value of checkbox. You can toggle the checkbox with a label
<label for="toggler1" class="toggler__label">Toggle here</label>
<input type="checkbox" class="toggler__input" id="toggler1" checked>
<div class="toggler__togglable">
Togglable size
</div>
Here are the styles:
.toggler__label {
cursor: pointer;
}
.toggler__input {
display: none;
}
.toggler__togglable {
font-size: 1.5rem;
}
.toggler__input:checked + .toggler__togglable {
font-size: 2rem;
}
Togglable size
If you just want to show and hide a block of content right after your label you can use details.
While you don’t want the address bar and browser’s history littered on any changes of visual state, sometimes you do. Like when looking at a gallery of images. If so, you can select the relevant element using the :target
pseudo-class.
<a href="#image1">
<img
class="gallery__image"
id="image1"
src="http://placecorgi.com/100/300?a" >
</a>
<a href="#image2">
<img
class="gallery__image"
id="image2"
src="http://placecorgi.com/100/300?b" >
</a>
<a href="#image3">
<img
class="gallery__image"
id="image3"
src="http://placecorgi.com/100/300?c" >
</a>
.gallery__image {
width: 100px;
height: 100px;
object-fit: cover;
}
.gallery__image:target {
width: 300px;
height: auto;
object-fit: none;
}
You can create both lightboxes and galleries like that. By the way that final example is from the February of 2012.
If you use HTML validation, you can also style your inputs using :valid
, :invalid
and other pseudo-classes.
<input class="validation__range" type="number" min="1" max="3" value="7">
<span class="validation__error">The number should be between 1 and 3.</span>
<input class="validation__invalid" type="email" placeholder="enter an email" required>
.validation__error {
display: none;
}
.validation__range:out-of-range + .validation__error {
display: inline;
}
input:required {
border: 1px orange solid;
}
.validation__invalid:valid {
border-bottom: 3px green solid;
}
The number should be between 1 and 3.
Many people are not aware that the elements like head, style, script and others are invisible just because the default stylesheet on their browser says so. You can view all of it inside the viewport if you want to.
Here’s an example styling to display style, link and script elements and their sources.
head, script, style, link { display: block }
script, style { white-space: pre }
link::before { content: attr(href) }
style::before { content: attr(src) }
There is a million things that can be done with just CSS. Including games like this and this or even without any HTML.
Recall the toggling example above? You could as well use radio buttons instead and make yourself a carousel. You don’t even have to put every input just before the content, you can .carousel__input:nth-of-type(4) ~ .carousel__slide:nth-of-type(4)
to have fourth input correspond to fourth slide. And in that way you refer to multiple elements that you style according to state of that input.
Did you notice the jQuery examples on this page? They are pure HTML. It’s the details
element.
<details>
<summary>The summary or spoiler label or...</summary>
The super thorough explanation
</details>
If you want to display a preview of PDF file, a lot of browsers have an embedded PDF viewer. There are three ways to trigger that inline without any plugins.
<object data="test.pdf"></object>
<iframe src="test.pdf"></iframe>
<embed src="test.pdf"></embed>
Did you notice that none of the JavaScript examples had any semicolons? There is a feature called automatic semicolon insertion (ASI) in JS. You can read up on it yourself, but a general rule is that you don’t need to terminate your statements with a semicolon unless you start the next line with a [
or (
.
Some the HTML tags are optional to close. It’s often to see <p>
without a closing tag. But body
and html
are also optional to close. And is there ever a point to have </body>
and </html>
included in your document? It’s even optional to close head
. You can just start the body.
Other tags that are closed automatically include li
, option
, dt
, dd
, tr
, th
, td
, thead
, tbody
, tfoot
. This is not a complete list but these are the common ones that are often more readable if just followed by the next sibling instead of closing tag.
Here is a nice example from WHATWG that shows how readable is a table without closing tags inside.
<table>
<caption>37547 TEE Electric Powered Rail Car Train Functions (Abbreviated)
<colgroup><col><col><col>
<thead>
<tr> <th>Function <th>Control Unit <th>Central Station
<tbody>
<tr> <td>Headlights <td>✔ <td>✔
<tr> <td>Interior Lights <td>✔ <td>✔
<tr> <td>Electric locomotive operating sounds <td>✔ <td>✔
<tr> <td>Engineer's cab lighting <td> <td>✔
<tr> <td>Station Announcements - Swiss <td> <td>✔
</table>
Some of the opening tags are also optional. For example, many of us have written a table with tree such as this.
<table>
<tr> <td>first <td>second <td>third
<tr> <td>fourth <td>fifth <td>sixth
</table>
Have you noticed that the rows get wrapped in a tbody
automatically? It turns out that both the opening and closing tags of tbody
are optional - you don’t have to specify them if you want them left at the default positions.
And you actually don’t have to include <html>
, <head>
and <body>
… with the caveat that you need to specify tag if you want any attributes there and you usually want <html lang=..>
.
A more complete discussion on tag omission can be found here at WHATWG.
Some guidelines even suggest skipping all of the optional tags. I do the same unless I’m working with Vue.
Here are some links that I find useful or just like. Check them out, you might find something useful.