前端MutationObserver监听DOM树变化

57 阅读8分钟

前端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">
                    &lt;script src="element-observer.js"&gt;&lt;/script&gt;
                </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>