- API to interact with a markup document, e.g. HTML, to change content, structure and styling programmatically
- language independent, but mainly used with JS
- represents document as logical structure, everything in document is a node, can visualize as tree-like structure, often refers by “DOM” to this “DOM tree”
- nodes are represented as objects, can be manipulated via their properties
- “The DOM is one of the worst APIs ever imagined.” - Douglas Crockford
- beware: minimize changes to DOM because blocks main thread, e.g. add single fragment with nodes to DOM, instead of each node individually ⚠️
document
object
- represents document
- entry point to DOM, all nodes are descendants (as nodes) of it, is the main node
(“root node” is already used for<html>
node) - beware: don’t confuse DOM tree of node objects with their inheritance chain, are completely seperate things
Document.dir
: direction of reading, “ltr” or “rtl”Document.title
: document title
Nodes
- everything in document is a node, i.e. (HTML) elements, text, comments, the doctype, and the document itself
- only element nodes, the document node, and the root document node (
<html>
element) can have children, i.e. text and comment nodes are always leaf nodes - any whitespace after
<head>
is a / part of a text node, usually not visible on page, dev tools also don’t show, shows between inline-block elements, can change using CSS propertywhite-space
- when computing the DOM, the browser autocorrets sloppy HTML markup, e.g. adds missing
<html>
,<head>
,<body>
,<tbody>
, etc. - JS can create nodes and add them to the DOM, enables possibility to manipulate every part of web page
Node
object type
- general node type, ancestor (as object) to all other node object types, inheritance chain
graph TD
A[Node] --> Text
A --> Comment
A --> B[Element]
A --> Document
A --> _1[...]
B --> SVGElement
B --> C[HTMLElement]
B --> _2[...]
C --> HTMLAnchorElement
C --> HTMLBodyElement
C --> HTMLInputElement
C --> _3[...]
- beware: don’t confuse DOM tree of node objects with their inheritance chain, are completely seperate things, e.g.
Document
inherits fromNode
, but properties ofDocument
are only available to its single instancedocument
instead of all objects that can be accessed throughdocument
Node.nodeName
: node name as stringNode.nodeType
: node type as integerNode.nodeValue
: node value as string or null
Node object types
object type | nodeName |
nodeType |
nodeValue |
---|---|---|---|
Element |
<tag name> | 1 | null |
Text |
#text | 3 | <content> |
Comment |
#comment | 8 | <content> |
Document |
#document | 9 | null |
DocumentType |
<doctype> | 10 | null |
DocumentFragment |
#document-fragment | 11 | null |
(this text uses abbreviation of first letter, e.g. “E” for Element
, “DF” for DocumentFragment
)
DocumentFragment
: temporary container for transfering nodes to DOM, only its content is appended to DOM, is never part of DOM itself, its whole purpose is to transfer nodes- several mixins are implemented in node types that share functionality
ParentNode
in E, D, DFNonElementParentNode
in D, DFChildNode
in E, C, T, DTNonDocumentTypeChildNode
in E, C, T
- collection objects:
NodeList
: array-like object, has.forEach()
HTMLCollection
: array-like object, has no.forEach()
, usefor..of
to iterate
DOM tree
Example HTML file
<!-- test -->
<!DOCTYPE html>
<html>
<head></head>↵
<body>↵
␣␣<p>Hello World!</p>↵
</body>
</html>
Example DOM tree
nodeName
- #document
- #comment
- html
- HTML
- HEAD
- #text
- BODY
- #text
- P
- #text
- #text
- HEAD
nodeValue
- null
- ␣test␣
- null
- null
- null
- ↵
- null
- ↵␣␣
- null
- Hello World!
- ↵
Walking the DOM
(in following chapters for readability names only “object type” instead of correctly writing “instance of object type”, e.g. ”Node
” instead of “instance of Node
“)
Nodes
Node.childNodes
: liveNodeList
of childNode
s, (Node
can only beParentNode
)Node.firstChild
: first childNode
, first item ofNode.childNodes
, (Node
can only beParentNode
)Node.lastChild
: last childNode
, last item ofNode.childNodes
, (Node
can only beParentNode
)Node.nextSibling
: next adjacentNode
Node.previousSibling
: previous adjacentNode
Node.parentNode
: parentNode
, (can only beParentNode
)
Elements
ParentNode.children
: liveHTMLCollection
of childElement
sParentNode.firstElementChild
: first childElement
, first item ofParentNode.children
ParentNode.lastElementChild
: last childElement
, last item ofParentNode.children
NonDocumentTypeChildNode.nextElementSibling
: next adjacentElement
NonDocumentTypeChildNode.previousElementSibling
: previous adjacentElement
Node.parentElement
: parentElement
Special nodes
Document.doctype
: the single instance ofDocumentType
Document.documentElement
: the single instance ofHTMLHtmlElement
Document.head
: the single instance ofHTMLHeadElement
Document.body
: the single instance ofHTMLBodyElement
Special elements
Document.forms
:HTMLCollection
of allHTMLFormElement
s, 🚫Document.images
:HTMLCollection
of allHTMLImageElement
s, 🚫Document.links
:HTMLCollection
of allHTMLAnchorElement
andHTMLAreaElement
s, 🚫Document.scripts
:HTMLCollection
of allHTMLScriptElement
s, 🚫DocumentOrShadowRoot.styleSheets
:StyleSheetList
of allCSSStyleSheet
s, 🚫
Searching the DOM
ParentNode.querySelector()
: first matching descendantElement
, staticParentNode.querySelectorAll()
:NodeList
of all descendantElement
s, staticParentNode.getElementsByTagName()
: liveHTMLCollection
of matching descendantElement
s, *ParentNode.getElementsByClassName()
: liveHTMLCollection
of matching descendantElement
s, *ParentNode.getElementsByName()
: liveHTMLCollection
of matching descendantElement
s, 🚫NonElementParentNode.getElementById()
: single matchingElement
Element.closest()
: first matching ancestorElement
including itselfElement.matches()
: ifElement
itself matches selector, boolean
(* as of Jan 2020 actually still implemented seperately in E, D, DF instead via mixin like written here)
- beware:
.getElementById()
is singular “Element” without “s” and lowercase “Id” with small “d” - beware: only the
.querySelector
/All()
methods takes a CSS selector as string, the other ones just the name as string!
Modifying the DOM
Creating nodes
Document.createComment()
: creates newComment
Document.createDocumentFragment()
: creates newDocumentFragment
Document.createElement()
: creates newHTMLElement
Document.createTextNode()
: creates newText
, (note the extra “Node” suffix)
Moving nodes
Node.appendChild()
: adds node to end of child nodes, if node is already part of DOM just moves it, if node is DF appends only its content, (Node
can only be validParentNode
)Node.insertBefore()
: inserts node before reference node in child nodes, if node is already part of DOM just moves it, if node is DF appends only its content, (Node
can only be validParentNode
)Node.cloneNode()
: creates duplicate node with or without its content, with all attributes, i.e. beware of duplicate IDs, inline event listeners, etc.Node.removeChild()
: removes node from child nodes, returns reference to removed node, (Node
can only be validParentNode
)Node.replaceChild()
: replaces node in child nodes, returns reference to replaced node, (Node
can only be validParentNode
)
Moving nodes (new)
ParentNode.append()
: likeNode.appendChild()
, but newerParentNode.prepend()
: likeParentNode.append()
, but to beginningChildNode.before()
: inserts node(s) before this node within parentChildNode.after()
: inserts node(s) after this node within parentChildNode.remove()
: removes this node from parentChildNode.replaceWith()
: replaces this node within parent with node(s)
Modifying nodes
Attributes
Element.attributes
: liveNamedNodeMap
containing all attributes, 👎Element.hasAttribute
/s()
: if contains specified / any attribute, booleanElement.getAttributeNames()
: attribute names, array of strings, 👍Element.getAttribute()
: attribute value, string, 👍Element.setAttribute()
: sets or creates attribute, returns undefined, 👍Element.removeAttribute()
: removes attribute, returns undefined, 👍
Classes
Element.classList
: liveDOMTokenList
containing all class attribute values, modify using itsadd()
/remove()
methods, 👍Element.className
: entire class attribute value, string, (should have been called “class” but it was a reserved keyword at the time it was created), 👎
Content
Element.innerHTML
: serialized HTML of content, staticElement.outerHTML
: serialized HTML of element and content, staticNode.textContent
: concatenation of all descendantText
nodes, i.e. including text in hidden elements,<style>
and<script>
elements, etc.HTMLElement.innerText
: only rendered text content
Special attributes
HTMLElement.dir
: direction of reading, “lrt” or “rtl” or “auto”HTMLElement.hidden
: ifhidden
attribute is present, booleanHTMLElement.style
: inline style, returnsCSSStyleDeclaration
, set/get its properties using camelCasing instead of hyphen, e.g.myelement.style.color
, value must be string with unitHTMLElement.title
: title attribute value
Notes on DOM methods
ParentNode.querySelector
/All()
is only sugar onNonElementParentNode.getElementsBy*()
, less performant, also not liveNode.appendChild()
forces repaint ifNode
is part of the DOM, i.e. for appending lots of nodes in a loop better append them to a DF first and then the DF once to DOM- create elements by setting HTML content of parent element with
Element.innerHTML
to template string, easier and faster than manually creating nodes, modifying their content and attributes, and appending them to element
Resources
- MDN - as usual
- The Modern Javascript Tutorial - as usual
- Ian Hickson - Live DOM Viewer