前端MutationObserver监听DOM树变化
这个通用元素变化监听工具提供了以下功能:
备注:文章由deepseek生成,在此整理笔记留档方便以后学习和回顾
0. 核心代码
// 元素变化监听工具类
class ElementChangeObserver {
constructor(targetElement, callback, options = {}) {
this.targetElement = targetElement;
this.callback = callback;
this.options = {
observeAttributes: true,
observeStyle: true,
observeClass: true,
observeRemoval: true,
observeCreation: true,
attributeFilter: [],
subtree: true,
childList: true,
...options
};
this.observer = null;
this.isObserving = false;
}
start() {
if (this.isObserving) return;
const config = {
attributes: this.options.observeAttributes || this.options.observeStyle || this.options.observeClass,
attributeFilter: this.getAttributeFilter(),
attributeOldValue: true,
childList: this.options.childList,
subtree: this.options.subtree
};
this.observer = new MutationObserver((mutations) => {
const changes = [];
mutations.forEach((mutation) => {
// 处理属性变化
if (mutation.type === 'attributes') {
if (this.options.observeAttributes &&
(!this.options.attributeFilter.length ||
this.options.attributeFilter.includes(mutation.attributeName))) {
changes.push({
type: 'attribute',
target: mutation.target,
details: {
attributeName: mutation.attributeName,
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute(mutation.attributeName)
}
});
}
// 处理样式变化
if (this.options.observeStyle && mutation.attributeName === 'style') {
changes.push({
type: 'style',
target: mutation.target,
details: {
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute('style')
}
});
}
// 处理类名变化
if (this.options.observeClass && mutation.attributeName === 'class') {
changes.push({
type: 'class',
target: mutation.target,
details: {
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute('class')
}
});
}
}
// 处理子节点变化
if (mutation.type === 'childList') {
// 处理元素移除
if (this.options.observeRemoval && mutation.removedNodes.length > 0) {
mutation.removedNodes.forEach((node) => {
if (node.nodeType === 1) { // 元素节点
changes.push({
type: 'removal',
target: node,
details: {
parent: mutation.target
}
});
}
});
}
// 处理元素创建
if (this.options.observeCreation && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) { // 元素节点
changes.push({
type: 'creation',
target: node,
details: {
parent: mutation.target
}
});
}
});
}
}
});
if (changes.length > 0 && typeof this.callback === 'function') {
this.callback(changes);
}
});
this.observer.observe(this.targetElement, config);
this.isObserving = true;
}
stop() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
this.isObserving = false;
}
getAttributeFilter() {
const filters = [];
if (this.options.observeAttributes && this.options.attributeFilter.length) {
filters.push(...this.options.attributeFilter);
}
if (this.options.observeStyle) {
filters.push('style');
}
if (this.options.observeClass) {
filters.push('class');
}
// 去重
return [...new Set(filters)];
}
}
1. 核心功能
- 元素创建监听:检测新元素的添加
- 元素移除监听:检测元素的移除
- 属性变化监听:检测元素属性的变化
- 样式变化监听:检测元素样式的变化
- 类名变化监听:检测元素类名的变化
2. 使用方式
javascript
// 创建监听器实例
const observer = new ElementChangeObserver(
element, // 要监听的元素
callback, // 变化回调函数
options // 配置选项
);
// 开始监听
observer.start();
// 停止监听
observer.stop();
3. 配置选项
javascript
const options = {
observeAttributes: true, // 监听属性变化
observeStyle: true, // 监听样式变化
observeClass: true, // 监听类名变化
observeRemoval: true, // 监听元素移除
observeCreation: true, // 监听元素创建
attributeFilter: [], // 指定要监听的属性
subtree: true, // 是否监听子树变化
childList: true // 是否监听子节点变化
};
4. 回调函数
回调函数接收一个变化数组,每个变化对象包含:
type:变化类型(attribute|style|class|removal|creation)target:目标元素details:变化详情
5. 页面功能
- 可视化界面演示工具功能
- 实时日志显示所有变化
- 可配置的监听选项
- 代码示例和使用说明
6. 全部代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通用元素变化监听工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
padding: 20px;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 30px;
padding: 20px;
background: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
h1 {
color: #2c3e50;
margin-bottom: 10px;
}
.description {
color: #7f8c8d;
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
}
.main-content {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.panel {
flex: 1;
min-width: 300px;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.panel h2 {
color: #3498db;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #f1f1f1;
}
.control-group {
margin-bottom: 20px;
}
.control-group h3 {
margin-bottom: 10px;
color: #2c3e50;
}
button {
padding: 10px 15px;
background: #3498db;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
margin: 5px;
}
button:hover {
background: #2980b9;
}
.danger {
background: #e74c3c;
}
.danger:hover {
background: #c0392b;
}
.success {
background: #2ecc71;
}
.success:hover {
background: #27ae60;
}
.elements-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 15px 0;
min-height: 100px;
padding: 15px;
border: 2px dashed #ddd;
border-radius: 5px;
}
.element {
padding: 15px;
background: #3498db;
color: white;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
width: 100px;
height: 60px;
}
.element:hover {
transform: scale(1.05);
}
.element.highlight {
outline: 2px solid #f39c12;
box-shadow: 0 0 10px rgba(243, 156, 18, 0.5);
}
.element.style-changed {
animation: pulse 0.5s;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.log-container {
max-height: 300px;
overflow-y: auto;
padding: 15px;
background: #f8f9fa;
border-radius: 5px;
border: 1px solid #eee;
margin-top: 20px;
}
.log-entry {
padding: 8px 12px;
margin-bottom: 8px;
border-radius: 4px;
font-size: 14px;
}
.removal-log {
background: #ffebee;
border-left: 4px solid #e74c3c;
}
.style-log {
background: #e8f5e9;
border-left: 4px solid #2ecc71;
}
.class-log {
background: #e3f2fd;
border-left: 4px solid #3498db;
}
.attribute-log {
background: #f3e5f5;
border-left: 4px solid #9b59b6;
}
.creation-log {
background: #fff3e0;
border-left: 4px solid #ff9800;
}
.debug-log {
background: #e0f7fa;
border-left: 4px solid #00bcd4;
}
.timestamp {
color: #7f8c8d;
font-size: 12px;
margin-right: 8px;
}
.slider-container {
margin: 15px 0;
}
.slider-container label {
display: block;
margin-bottom: 5px;
}
input[type="range"] {
width: 100%;
}
.color-picker {
display: flex;
gap: 10px;
margin: 10px 0;
flex-wrap: wrap;
}
.color-option {
width: 30px;
height: 30px;
border-radius: 50%;
cursor: pointer;
transition: transform 0.2s;
}
.color-option:hover {
transform: scale(1.2);
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-active {
background-color: #2ecc71;
}
.status-inactive {
background-color: #e74c3c;
}
.code-container {
background: #2c3e50;
color: #ecf0f1;
padding: 15px;
border-radius: 5px;
margin: 15px 0;
overflow-x: auto;
font-family: 'Courier New', monospace;
}
.tab-buttons {
display: flex;
margin-bottom: 10px;
}
.tab-button {
padding: 8px 15px;
background: #34495e;
color: #ecf0f1;
border: none;
cursor: pointer;
margin-right: 5px;
border-radius: 3px 3px 0 0;
}
.tab-button.active {
background: #2c3e50;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
@media (max-width: 768px) {
.main-content {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>通用元素变化监听工具</h1>
<p class="description">
这是一个强大的元素变化监听工具,可以监听元素的创建、移除、属性变化、样式变化等。
</p>
</header>
<div class="main-content">
<div class="panel">
<h2>元素控制</h2>
<div class="control-group">
<h3>可操作元素</h3>
<div class="elements-container" id="elementsContainer">
<div class="element" id="element1" data-custom="value1">元素 1</div>
<div class="element" id="element2" data-custom="value2">元素 2</div>
<div class="element" id="element3" data-custom="value3">元素 3</div>
</div>
<button id="addElement">添加新元素</button>
</div>
<div class="control-group">
<h3>移除元素</h3>
<button id="removeFirst">移除第一个元素</button>
<button id="removeRandom">随机移除元素</button>
</div>
<div class="control-group">
<h3>样式修改</h3>
<div class="slider-container">
<label for="widthSlider">宽度: <span id="widthValue">100</span>px</label>
<input type="range" id="widthSlider" min="50" max="200" value="100">
</div>
<div class="slider-container">
<label for="heightSlider">高度: <span id="heightValue">60</span>px</label>
<input type="range" id="heightSlider" min="30" max="150" value="60">
</div>
<div>
<label>背景颜色:</label>
<div class="color-picker">
<div class="color-option" style="background-color: #3498db;" data-color="#3498db"></div>
<div class="color-option" style="background-color: #2ecc71;" data-color="#2ecc71"></div>
<div class="color-option" style="background-color: #e74c3c;" data-color="#e74c3c"></div>
<div class="color-option" style="background-color: #f39c12;" data-color="#f39c12"></div>
<div class="color-option" style="background-color: #9b59b6;" data-color="#9b59b6"></div>
</div>
</div>
<div style="margin-top: 15px;">
<button id="addClass">添加.highlight类</button>
<button id="removeClass">移除.highlight类</button>
</div>
</div>
<div class="control-group">
<h3>属性修改</h3>
<button id="changeDataAttr">修改data-custom属性</button>
<button id="changeTitle">修改title属性</button>
</div>
</div>
<div class="panel">
<h2>变化日志 <span id="statusIndicator" class="status-indicator status-active"></span></h2>
<div class="log-container" id="logContainer">
<div class="log-entry">
<span class="timestamp">[系统]</span>
日志初始化完成,等待变化...
</div>
</div>
<div style="margin-top: 20px;">
<button id="clearLog" class="danger">清除日志</button>
<button id="startObserving" class="success">开始监听</button>
<button id="stopObserving">停止监听</button>
</div>
<div style="margin-top: 20px;">
<h3>监听选项</h3>
<div>
<input type="checkbox" id="observeStyle" checked>
<label for="observeStyle">监听样式变化</label>
</div>
<div>
<input type="checkbox" id="observeClass" checked>
<label for="observeClass">监听类名变化</label>
</div>
<div>
<input type="checkbox" id="observeAttributes" checked>
<label for="observeAttributes">监听属性变化</label>
</div>
<div>
<input type="checkbox" id="observeRemoval" checked>
<label for="observeRemoval">监听元素移除</label>
</div>
<div>
<input type="checkbox" id="observeCreation" checked>
<label for="observeCreation">监听元素创建</label>
</div>
</div>
</div>
</div>
<div class="panel" style="margin-top: 20px;">
<h2>工具使用说明</h2>
<div class="tab-buttons">
<button class="tab-button active" data-tab="usage">使用方式</button>
<button class="tab-button" data-tab="options">配置选项</button>
<button class="tab-button" data-tab="examples">代码示例</button>
</div>
<div class="tab-content active" id="usage">
<h3>如何使用元素变化监听工具</h3>
<p>1. 引入工具脚本</p>
<div class="code-container">
<script src="element-observer.js"></script>
</div>
<p>2. 创建监听器实例</p>
<div class="code-container">
const observer = new ElementChangeObserver(element, callback, options);
</div>
<p>3. 开始监听</p>
<div class="code-container">
observer.start();
</div>
<p>4. 停止监听</p>
<div class="code-container">
observer.stop();
</div>
</div>
<div class="tab-content" id="options">
<h3>配置选项</h3>
<div class="code-container">
const options = {
observeAttributes: true, // 监听属性变化
observeStyle: true, // 监听样式变化
observeClass: true, // 监听类名变化
observeRemoval: true, // 监听元素移除
observeCreation: true, // 监听元素创建
attributeFilter: [], // 指定要监听的属性
subtree: true, // 是否监听子树变化
childList: true // 是否监听子节点变化
};
</div>
</div>
<div class="tab-content" id="examples">
<h3>代码示例</h3>
<p>基本使用示例:</p>
<div class="code-container">
// 创建监听器
const observer = new ElementChangeObserver(
document.getElementById('myElement'),
(changes) => {
changes.forEach(change => {
console.log('变化类型:', change.type);
console.log('目标元素:', change.target);
console.log('变化详情:', change.details);
});
},
{
observeAttributes: true,
observeStyle: true,
observeClass: true
}
);
// 开始监听
observer.start();
</div>
<p>只监听特定属性:</p>
<div class="code-container">
const observer = new ElementChangeObserver(
element,
callback,
{
observeAttributes: true,
attributeFilter: ['data-custom', 'title'] // 只监听这些属性
}
);
</div>
</div>
</div>
</div>
<script>
// 元素变化监听工具类
class ElementChangeObserver {
constructor(targetElement, callback, options = {}) {
this.targetElement = targetElement;
this.callback = callback;
this.options = {
observeAttributes: true,
observeStyle: true,
observeClass: true,
observeRemoval: true,
observeCreation: true,
attributeFilter: [],
subtree: true,
childList: true,
...options
};
this.observer = null;
this.isObserving = false;
}
start() {
if (this.isObserving) return;
const config = {
attributes: this.options.observeAttributes || this.options.observeStyle || this.options.observeClass,
attributeFilter: this.getAttributeFilter(),
attributeOldValue: true,
childList: this.options.childList,
subtree: this.options.subtree
};
this.observer = new MutationObserver((mutations) => {
const changes = [];
mutations.forEach((mutation) => {
// 处理属性变化
if (mutation.type === 'attributes') {
if (this.options.observeAttributes &&
(!this.options.attributeFilter.length ||
this.options.attributeFilter.includes(mutation.attributeName))) {
changes.push({
type: 'attribute',
target: mutation.target,
details: {
attributeName: mutation.attributeName,
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute(mutation.attributeName)
}
});
}
// 处理样式变化
if (this.options.observeStyle && mutation.attributeName === 'style') {
changes.push({
type: 'style',
target: mutation.target,
details: {
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute('style')
}
});
}
// 处理类名变化
if (this.options.observeClass && mutation.attributeName === 'class') {
changes.push({
type: 'class',
target: mutation.target,
details: {
oldValue: mutation.oldValue,
newValue: mutation.target.getAttribute('class')
}
});
}
}
// 处理子节点变化
if (mutation.type === 'childList') {
// 处理元素移除
if (this.options.observeRemoval && mutation.removedNodes.length > 0) {
mutation.removedNodes.forEach((node) => {
if (node.nodeType === 1) { // 元素节点
changes.push({
type: 'removal',
target: node,
details: {
parent: mutation.target
}
});
}
});
}
// 处理元素创建
if (this.options.observeCreation && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) { // 元素节点
changes.push({
type: 'creation',
target: node,
details: {
parent: mutation.target
}
});
}
});
}
}
});
if (changes.length > 0 && typeof this.callback === 'function') {
this.callback(changes);
}
});
this.observer.observe(this.targetElement, config);
this.isObserving = true;
}
stop() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
this.isObserving = false;
}
getAttributeFilter() {
const filters = [];
if (this.options.observeAttributes && this.options.attributeFilter.length) {
filters.push(...this.options.attributeFilter);
}
if (this.options.observeStyle) {
filters.push('style');
}
if (this.options.observeClass) {
filters.push('class');
}
// 去重
return [...new Set(filters)];
}
}
// 页面初始化
document.addEventListener('DOMContentLoaded', function() {
// 获取DOM元素
const elementsContainer = document.getElementById('elementsContainer');
const logContainer = document.getElementById('logContainer');
const addElementBtn = document.getElementById('addElement');
const removeFirstBtn = document.getElementById('removeFirst');
const removeRandomBtn = document.getElementById('removeRandom');
const widthSlider = document.getElementById('widthSlider');
const heightSlider = document.getElementById('heightSlider');
const widthValue = document.getElementById('widthValue');
const heightValue = document.getElementById('heightValue');
const colorOptions = document.querySelectorAll('.color-option');
const addClassBtn = document.getElementById('addClass');
const removeClassBtn = document.getElementById('removeClass');
const changeDataAttrBtn = document.getElementById('changeDataAttr');
const changeTitleBtn = document.getElementById('changeTitle');
const clearLogBtn = document.getElementById('clearLog');
const startObservingBtn = document.getElementById('startObserving');
const stopObservingBtn = document.getElementById('stopObserving');
const statusIndicator = document.getElementById('statusIndicator');
const observeStyleCheckbox = document.getElementById('observeStyle');
const observeClassCheckbox = document.getElementById('observeClass');
const observeAttributesCheckbox = document.getElementById('observeAttributes');
const observeRemovalCheckbox = document.getElementById('observeRemoval');
const observeCreationCheckbox = document.getElementById('observeCreation');
const tabButtons = document.querySelectorAll('.tab-button');
// 当前选中的元素和观察者
let selectedElement = null;
let elementObserver = null;
// 初始化元素选择
function initElementSelection() {
const elements = document.querySelectorAll('.element');
elements.forEach(element => {
element.addEventListener('click', function(e) {
// 阻止事件冒泡,避免多次触发
e.stopPropagation();
// 移除之前选中的元素的高亮
if (selectedElement) {
selectedElement.classList.remove('highlight');
}
// 高亮当前选中的元素
this.classList.add('highlight');
selectedElement = this;
addLog('选中了元素: ' + this.id, 'style');
});
});
}
// 初始化
initElementSelection();
// 添加新元素
addElementBtn.addEventListener('click', function() {
const elementCount = document.querySelectorAll('.element').length;
const newElement = document.createElement('div');
newElement.className = 'element';
newElement.id = 'element' + (elementCount + 1);
newElement.textContent = '元素 ' + (elementCount + 1);
newElement.setAttribute('data-custom', 'value' + (elementCount + 1));
elementsContainer.appendChild(newElement);
initElementSelection();
addLog('添加了新元素: ' + newElement.id, 'creation');
});
// 移除第一个元素
removeFirstBtn.addEventListener('click', function() {
const firstElement = elementsContainer.querySelector('.element');
if (firstElement) {
// 如果移除的是当前选中的元素,清除选中状态
if (selectedElement === firstElement) {
selectedElement = null;
}
elementsContainer.removeChild(firstElement);
} else {
addLog('没有元素可以移除', 'removal');
}
});
// 随机移除元素
removeRandomBtn.addEventListener('click', function() {
const elements = document.querySelectorAll('.element');
if (elements.length > 0) {
const randomIndex = Math.floor(Math.random() * elements.length);
const randomElement = elements[randomIndex];
// 如果移除的是当前选中的元素,清除选中状态
if (selectedElement === randomElement) {
selectedElement = null;
}
elementsContainer.removeChild(randomElement);
} else {
addLog('没有元素可以移除', 'removal');
}
});
// 宽度滑块
widthSlider.addEventListener('input', function() {
widthValue.textContent = this.value;
if (selectedElement) {
selectedElement.style.width = this.value + 'px';
}
});
// 高度滑块
heightSlider.addEventListener('input', function() {
heightValue.textContent = this.value;
if (selectedElement) {
selectedElement.style.height = this.value + 'px';
}
});
// 颜色选择
colorOptions.forEach(option => {
option.addEventListener('click', function() {
if (selectedElement) {
selectedElement.style.backgroundColor = this.dataset.color;
}
});
});
// 添加类
addClassBtn.addEventListener('click', function() {
if (selectedElement) {
selectedElement.classList.add('highlight');
}
});
// 移除类
removeClassBtn.addEventListener('click', function() {
if (selectedElement) {
selectedElement.classList.remove('highlight');
}
});
// 修改data属性
changeDataAttrBtn.addEventListener('click', function() {
if (selectedElement) {
const currentValue = selectedElement.getAttribute('data-custom');
const newValue = currentValue ? currentValue + '-modified' : 'modified';
selectedElement.setAttribute('data-custom', newValue);
}
});
// 修改title属性
changeTitleBtn.addEventListener('click', function() {
if (selectedElement) {
const currentValue = selectedElement.getAttribute('title');
const newValue = currentValue ? currentValue + ' modified' : '元素标题';
selectedElement.setAttribute('title', newValue);
}
});
// 清除日志
clearLogBtn.addEventListener('click', function() {
logContainer.innerHTML = '';
addLog('日志已清除', 'style');
});
// 开始监听
startObservingBtn.addEventListener('click', function() {
startObservation();
});
// 停止监听
stopObservingBtn.addEventListener('click', function() {
if (elementObserver) {
elementObserver.stop();
elementObserver = null;
statusIndicator.className = 'status-indicator status-inactive';
addLog('已停止监听DOM变化', 'removal');
}
});
// 标签切换
tabButtons.forEach(button => {
button.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');
// 移除所有active类
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// 添加active类到当前标签
this.classList.add('active');
document.getElementById(tabId).classList.add('active');
});
});
// 添加日志函数
function addLog(message, type) {
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}-log`;
const timestamp = new Date().toLocaleTimeString();
logEntry.innerHTML = `<span class="timestamp">[${timestamp}]</span> ${message}`;
logContainer.appendChild(logEntry);
// 自动滚动到底部
logContainer.scrollTop = logContainer.scrollHeight;
// 同时在控制台输出
console.log(`[${type}] ${message}`);
}
// 初始化元素变化监听
function startObservation() {
if (elementObserver) {
elementObserver.stop();
}
// 创建监听选项
const options = {
observeAttributes: observeAttributesCheckbox.checked,
observeStyle: observeStyleCheckbox.checked,
observeClass: observeClassCheckbox.checked,
observeRemoval: observeRemovalCheckbox.checked,
observeCreation: observeCreationCheckbox.checked,
attributeFilter: ['data-custom', 'title'],
subtree: true,
childList: true
};
// 创建监听器实例
elementObserver = new ElementChangeObserver(
elementsContainer,
(changes) => {
changes.forEach(change => {
let message = '';
switch(change.type) {
case 'attribute':
message = `属性变化: ${change.target.id || '未知元素'} 的 ${change.details.attributeName} 属性`;
message += ` (旧值: ${change.details.oldValue || '空'}, 新值: ${change.details.newValue || '空'})`;
break;
case 'style':
message = `样式变化: ${change.target.id || '未知元素'}`;
break;
case 'class':
message = `类名变化: ${change.target.id || '未知元素'}`;
message += ` (旧值: ${change.details.oldValue || '空'}, 新值: ${change.details.newValue || '空'})`;
break;
case 'removal':
message = `元素被移除: ${change.target.id || '未知元素'}`;
break;
case 'creation':
message = `元素被创建: ${change.target.id || '未知元素'}`;
break;
}
addLog(message, change.type);
});
},
options
);
// 开始监听
elementObserver.start();
statusIndicator.className = 'status-indicator status-active';
addLog('开始监听DOM变化', 'style');
// 输出调试信息
console.log('开始监听DOM变化,配置:', options);
}
// 默认开始监听
startObservation();
// 添加点击容器取消选中的功能
elementsContainer.addEventListener('click', function(e) {
if (e.target === elementsContainer) {
if (selectedElement) {
selectedElement.classList.remove('highlight');
addLog('取消选中元素', 'style');
}
selectedElement = null;
}
});
});
</script>
</body>
</html>