Shadow DOM and Virtual DOM are two different technologies for working with the DOM that solve similar but different tasks. Let’s figure out what’s what! 🤔
✅ Shadow DOM:
✅ Virtual DOM:
Key difference: Shadow DOM is about isolation, while Virtual DOM is about optimization! 🔥
Imagine you’re building a house. Shadow DOM is like building a separate room with its own walls and door that no one can see from other rooms. Virtual DOM is like making a blueprint of the house so you don’t have to rebuild everything each time, but only make necessary changes!
Shadow DOM is a native browser technology that allows creating isolated DOM trees inside elements:
// Creating Shadow DOM
class MyElement extends HTMLElement {
constructor() {
super();
// Create Shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// The element's internals are isolated
shadow.innerHTML = `
<style>
p {
color: blue;
font-weight: bold;
}
</style>
<p>Hello from Shadow DOM! 🎉</p>
`;
}
}
// Register Web Component
customElements.define('my-element', MyElement);
// Usage
// <my-element></my-element>
Virtual DOM is a concept where a virtual representation of UI is created in memory:
// Example of how Virtual DOM works (simplified)
function render() {
// This is Virtual DOM - an object in memory
return {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: { children: 'Hello, world!' }
},
{
type: 'p',
props: { children: 'This is Virtual DOM in action!' }
}
]
}
};
}
// Library (React) compares old and new Virtual DOM
// And applies only necessary changes to the real DOM
Characteristic | Shadow DOM | Virtual DOM |
---|---|---|
Type | Native browser technology | Concept/pattern |
Purpose | Isolation and encapsulation | Update optimization |
Where used | Web Components | React, Vue, Angular, etc. |
Style isolation | ✅ Full | ❌ No |
Rendering optimization | ❌ No | ✅ Yes |
Learning complexity | Medium | Low |
Shadow DOM is perfect when you need:
// 1. Creating reusable Web Components
class DatePicker extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// Styles are fully isolated!
shadow.innerHTML = `
<style>
.calendar {
border: 1px solid #ccc;
border-radius: 4px;
padding: 10px;
}
/* These styles won't affect external elements */
</style>
<div class="calendar">
<!-- Calendar -->
</div>
`;
}
}
// 2. When style encapsulation is needed
class ButtonComponent extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
button {
background: blue;
color: white;
border: none;
padding: 8px 16px;
/* These styles don't conflict with globals */
}
</style>
<button>
<slot></slot> <!-- Slot for content -->
</button>
`;
}
}
Virtual DOM is your choice when:
// 1. Building complex interactive applications
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Buy milk', done: false },
{ id: 2, text: 'Walk the dog', done: true }
]);
// When state changes, Virtual DOM:
// 1. Creates a new virtual tree
// 2. Compares with the previous one
// 3. Applies only necessary changes
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
return (
<div>
{todos.map(todo => (
<div key={todo.id} className={todo.done ? 'done' : ''}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
</div>
))}
</div>
);
}
// 2. When update performance matters
function ExpensiveList({ items }) {
// Virtual DOM optimizes list updates
// Instead of full redraw, only changed items are updated
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name} - {item.value}
</li>
))}
</ul>
);
}
// Creating a custom element with Shadow DOM
class FancyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// Get attributes
const title = this.getAttribute('title') || 'Title';
const content = this.getAttribute('content') || 'Content';
shadow.innerHTML = `
<style>
.card {
border: 2px solid #3498db;
border-radius: 8px;
padding: 16px;
margin: 10px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
background: white;
transition: transform 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.title {
color: #2c3e50;
font-size: 1.2em;
margin-bottom: 8px;
font-weight: bold;
}
.content {
color: #7f8c8d;
}
</style>
<div class="card">
<div class="title">${title}</div>
<div class="content">${content}</div>
</div>
`;
}
}
// Register component
customElements.define('fancy-card', FancyCard);
// Usage in HTML:
// <fancy-card
// title="My Card"
// content="Beautiful card with style isolation! 🎨">
// </fancy-card>
// React component with Virtual DOM
function UserDashboard({ users }) {
const [filter, setFilter] = useState('');
// Filtering users
const filteredUsers = users.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase())
);
// Virtual DOM makes this efficient!
return (
<div className="dashboard">
<input
type="text"
placeholder="Search users..."
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<div className="user-list">
{filteredUsers.map(user => (
<div key={user.id} className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
))}
</div>
</div>
);
}
// When typing text:
// 1. filter state updates
// 2. filteredUsers recalculated
// 3. Virtual DOM compares old and new tree
// 4. Only necessary changes applied to real DOM
// ❌ Wrong - Shadow DOM doesn't optimize updates
class BadComponent extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this.render(); // Full redraw on every update
}
render() {
// This is inefficient for frequent updates!
this.shadow.innerHTML = `
<div>${this.getAttribute('data-value')}</div>
`;
}
}
// ✅ Correct - use Virtual DOM for frequent updates
function GoodComponent({ value }) {
// Virtual DOM optimizes updates automatically
return <div>{value}</div>;
}
// ❌ Style problem
class ProblematicElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
/* These styles might conflict if Shadow DOM isn't used properly */
.button {
background: red;
}
</style>
<button class="button">Button</button>
`;
}
}
// ✅ Proper encapsulation
class GoodElement extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
/* Styles are fully isolated in Shadow DOM */
button {
background: #3498db;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
}
</style>
<button>
<slot name="label">Button</slot>
</button>
`;
}
}
Shadow DOM and Virtual DOM are like two different superheroes! 🦸♂️🦸♀️
Shadow DOM - the isolation hero:
Virtual DOM - the optimization hero:
When to use what:
Both approaches make frontend development more powerful and convenient, just solving different tasks! 🚀
Want more awesome frontend articles? Subscribe to EasyAdvice, bookmark the site and level up every day! 💪