Morphdom
Fast and lightweight DOM diffing/patching (no virtual DOM needed)
Install / Use
/learn @patrick-steele-idem/MorphdomREADME
morphdom
Lightweight module for morphing an existing DOM node tree to match a target DOM node tree. It's fast and works with the real DOM—no virtual DOM needed!
This module was created to solve the problem of updating the DOM in response to a UI component or page being rerendered. One way to update the DOM is to simply toss away the existing DOM tree and replace it with a new DOM tree (e.g., myContainer.innerHTML = newHTML). While replacing an existing DOM tree with an entirely new DOM tree will actually be very fast, it comes with a cost. The cost is that all of the internal state associated with the existing DOM nodes (scroll positions, input caret positions, CSS transition states, etc.) will be lost. Instead of replacing the existing DOM tree with a new DOM tree we want to transform the existing DOM tree to match the new DOM tree while minimizing the number of changes to the existing DOM tree. This is exactly what the morphdom module does! Give it an existing DOM node tree and a target DOM node tree and it will efficiently transform the existing DOM node tree to exactly match the target DOM node tree with the minimum amount of changes.
morphdom does not rely on any virtual DOM abstractions. Because morphdom is using the real DOM, the DOM that the web browser is maintaining will always be the source of truth. Even if you have code that manually manipulates the DOM things will still work as expected. In addition, morphdom can be used with any templating language that produces an HTML string.
The transformation is done in a single pass of both the original DOM tree and the target DOM tree and is designed to minimize changes to the DOM while still ensuring that the morphed DOM exactly matches the target DOM. In addition, the algorithm used by this module will automatically match up elements that have corresponding IDs and that are found in both the original and target DOM tree.
Support for diffing the real DOM with a virtual DOM was introduced in v2.1.0. Virtual DOM nodes are expected to implement the minimal subset of the real DOM API required by morphdom and virtual DOM nodes are automatically upgraded real DOM nodes if they need to be moved into the real DOM. For more details, please see: docs/virtual-dom.md.
Usage
First install the module into your project:
npm install morphdom --save
NOTE: Published npm packages:
dist/morphdom-umd.jsdist/morphdom-esm.js
The code below shows how to morph one <div> element to another <div> element.
var morphdom = require('morphdom');
var el1 = document.createElement('div');
el1.className = 'foo';
var el2 = document.createElement('div');
el2.className = 'bar';
morphdom(el1, el2);
expect(el1.className).to.equal('bar');
You can also pass in an HTML string for the second argument:
var morphdom = require('morphdom');
var el1 = document.createElement('div');
el1.className = 'foo';
el1.innerHTML = 'Hello John';
morphdom(el1, '<div class="bar">Hello Frank</div>');
expect(el1.className).to.equal('bar');
expect(el1.innerHTML).to.equal('Hello Frank');
NOTE: This module will modify both the original and target DOM node tree during the transformation. It is assumed that the target DOM node tree will be discarded after the original DOM node tree is morphed.
Examples
See: ./examples/
Browser Support
- IE9+ and any modern browser
- Proper namespace support added in
v1.4.0
API
morphdom(fromNode, toNode, options) : Node
The morphdom(fromNode, toNode, options) function supports the following arguments:
- fromNode (
Node)- The node to morph - toNode (
Node|String) - The node that thefromNodeshould be morphed to (or an HTML string) - options (
Object) - See below for supported options
The returned value will typically be the fromNode. However, in situations where the fromNode is not compatible with the toNode (either different node type or different tag name) then a different DOM node will be returned.
Supported options (all optional):
- getNodeKey (
Function(node)) - Called to get theNode's unique identifier. This is used bymorphdomto rearrange elements rather than creating and destroying an element that already exists. This defaults to using theNode'sidproperty. (Note that form fields must not have anamecorresponding to forms' DOM properties, e.g.id.) - addChild (
Function(parentNode, childNode)) - Called when adding a new child to a parent. By default,parentNode.appendChild(childNode)is invoked. Use this callback to customize how a new child is added. - onBeforeNodeAdded (
Function(node)) - Called before aNodein thetotree is added to thefromtree. If this function returnsfalsethen the node will not be added. Should return the node to be added. - onNodeAdded (
Function(node)) - Called after aNodein thetotree has been added to thefromtree. - onBeforeElUpdated (
Function(fromEl, toEl)) - Called before aHTMLElementin thefromtree is updated. If this function returnsfalsethen the element will not be updated. if this function returns an instance ofHTMLElement, it will be used as the new fromEl tree to proceed with morphing for that branch, otherwise the current fromEl tree is used. - onElUpdated (
Function(el)) - Called after aHTMLElementin thefromtree has been updated. - onBeforeNodeDiscarded (
Function(node)) - Called before aNodein thefromtree is discarded. If this function returnsfalsethen the node will not be discarded. - onNodeDiscarded (
Function(node)) - Called after aNodein thefromtree has been discarded. - onBeforeElChildrenUpdated (
Function(fromEl, toEl)) - Called before the children of aHTMLElementin thefromtree are updated. If this function returnsfalsethen the child nodes will not be updated. - childrenOnly (
Boolean) - Iftruethen only the children of thefromNodeandtoNodenodes will be morphed (the containing element will be skipped). Defaults tofalse. - skipFromChildren (
Function(fromEl)) - called when indexing a thefromEltree. False by default. Returntrueto skip indexing the from tree, which will keep current items in place after patch rather than removing them when not found in thetoEl.
var morphdom = require('morphdom');
var morphedNode = morphdom(fromNode, toNode, {
getNodeKey: function(node) {
return node.id;
},
addChild: function(parentNode, childNode) {
parentNode.appendChild(childNode);
},
onBeforeNodeAdded: function(node) {
return node;
},
onNodeAdded: function(node) {
},
onBeforeElUpdated: function(fromEl, toEl) {
return true;
},
onElUpdated: function(el) {
},
onBeforeNodeDiscarded: function(node) {
return true;
},
onNodeDiscarded: function(node) {
},
onBeforeElChildrenUpdated: function(fromEl, toEl) {
return true;
},
childrenOnly: false,
skipFromChildren: function(fromEl, toEl) {
return false;
}
});
FAQ
Can I make morphdom blaze through the DOM tree even faster? Yes.
morphdom(fromNode, toNode, {
onBeforeElUpdated: function(fromEl, toEl) {
// spec - https://dom.spec.whatwg.org/#concept-node-equals
if (fromEl.isEqualNode(toEl)) {
return false
}
return true
}
})
This avoids traversing through the entire subtree when you know they are equal. While we haven't added this to the core lib yet due to very minor concerns, this is an easy way to make DOM diffing speeds on par with virtual DOM.
Isn't the DOM slow?
UPDATE: As of v2.1.0, morphdom supports both diffing a real DOM tree with another real DOM tree and diffing a real DOM tree with a virtual DOM tree. See: docs/virtual-dom.md for more details.
No, the DOM data structure is not slow. The DOM is a key part of any web browser so it must be fast. Walking a DOM tree and reading the attributes on DOM nodes is not slow. However, if you attempt to read a computed property on a DOM node that requires a relayout of the page then that will be slow. However, morphdom only cares about the following properties and methods of a DOM node:
node.firstChildnode.nextSiblingnode.nodeTypenode.nodeNamenode.nodeValuenode.attributesnode.valuenode.selectednode.disabledactualize(document)(non-standard, used to upgrade a virtual DOM node to a real DOM node)hasAttributeNS(namespaceURI, name)isSameNode(anotherNode)
What about the virtual DOM?
Libraries such as a React and virtual-dom solve a similar problem using a Virtual DOM. That is, at any given time there will be the real DOM (that the browser rendered) and a lightweight and persistent virtual DOM tree that is a mirror of the real DOM tree. Whenever the view needs to update, a new virtual DOM tree is rendered. The new virtual DOM tree is then compared with the old virtual DOM tree using a diffing algorithm. Based on the differences that are found, the real DOM is then "patched" to match the new virtual DOM tree and the new virtual DOM tree is persisted for future diffing.
Both morphdom and virtual DOM based solutions update the real DOM with the minimum number of changes. The only difference is in how the differences are determined. morphdom compares real DOM nodes while virtual-dom and others only compare virtual DOM nodes.
There are some drawbacks to using
Related Skills
node-connect
326.5kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
80.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
326.5kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
80.4kCommit, push, and open a PR

