pin_drop当前位置:知识文库 ❯ 图文

HTML5 API:HTML5 Resize Observer - 完整教程与代码示例

一、教程简介

Resize Observer API 提供了监听元素尺寸变化的能力,当被观察元素的边界框大小发生改变时,会触发回调函数。相比传统的 window.resize 事件,Resize Observer 可以监听任意元素(不仅仅是窗口)的尺寸变化,且不会因频繁触发而导致性能问题。该 API 广泛应用于响应式组件、自适应布局、图表重绘、虚拟滚动等场景。


二、核心概念

与 window.resize 对比

特性 window.resize Resize Observer
监听对象 仅窗口 任意元素
触发频率 高频 高频但可节流
信息获取 需手动 getBoundingClientRect 自动提供尺寸
元素隐藏时 仍触发 不触发
性能 需手动优化 浏览器优化

ResizeObserverEntry

属性 说明
target 被观察的目标元素
contentRect 内容区域的 DOMRectReadOnly
contentBoxSize 内容盒尺寸
borderBoxSize 边框盒尺寸

contentRect 属性

代码示例

entry.contentRect // DOMRectReadOnly
{
    x: 0,        // 相对于元素的 x 偏移
    y: 0,        // 相对于元素的 y 偏移
    width: 300,   // 内容宽度(不含 padding/border)
    height: 200,  // 内容高度(不含 padding/border)
    top: 0,
    right: 300,
    bottom: 200,
    left: 0
}

三、语法与用法

创建与观察

代码示例

const observer = new ResizeObserver(entries => {
    for (const entry of entries) {
        console.log('元素:', entry.target);
        console.log('宽度:', entry.contentRect.width);
        console.log('高度:', entry.contentRect.height);
    }
});

observer.observe(element);     // 开始观察
observer.unobserve(element);   // 停止观察
observer.disconnect();         // 停止所有观察

监听多个元素

代码示例

document.querySelectorAll('.resizable').forEach(el => {
    observer.observe(el);
});

使用 borderBoxSize

代码示例

const observer = new ResizeObserver(entries => {
    for (const entry of entries) {
        const size = entry.borderBoxSize[0];
        console.log('边框盒宽度:', size.inlineSize);
        console.log('边框盒高度:', size.blockSize);
    }
});

四、代码示例

示例一:响应式尺寸监控

以下是一个完整的响应式尺寸监控示例,展示了如何使用 Resize Observer 监听元素尺寸变化并实时更新界面:

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Resize Observer - 尺寸监控</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: #f5f7fa;
            min-height: 100vh;
            padding: 30px 20px;
        }
        .container { max-width: 800px; margin: 0 auto; }
        h1 { font-size: 24px; color: #2d3436; margin-bottom: 8px; }
        .desc { color: #636e72; font-size: 14px; margin-bottom: 24px; }
        .resizable-box {
            background: linear-gradient(135deg, #667eea, #764ba2);
            border-radius: 12px;
            padding: 24px;
            color: #fff;
            resize: both;
            overflow: hidden;
            min-width: 150px;
            min-height: 100px;
            max-width: 100%;
            margin-bottom: 16px;
            position: relative;
        }
        .size-badge {
            position: absolute;
            bottom: 8px;
            right: 8px;
            background: rgba(0,0,0,0.4);
            padding: 4px 10px;
            border-radius: 6px;
            font-size: 12px;
            font-family: monospace;
        }
        .card {
            background: #fff;
            border-radius: 12px;
            padding: 24px;
            margin-bottom: 16px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
        }
        .card h2 { font-size: 16px; color: #2d3436; margin-bottom: 16px; }
        .size-info {
            display: grid;
            grid-template-columns: repeat(4, 1fr);
            gap: 12px;
        }
        .size-item {
            background: #f8f9fa;
            border-radius: 8px;
            padding: 12px;
            text-align: center;
        }
        .size-label { font-size: 11px; color: #999; margin-bottom: 4px; }
        .size-value { font-size: 18px; font-weight: 700; color: #0984e3; }
        .log-area {
            background: #1e1e1e;
            border-radius: 8px;
            padding: 12px;
            max-height: 200px;
            overflow-y: auto;
            font-family: monospace;
            font-size: 12px;
            line-height: 1.7;
            color: #a0e0a0;
        }
        .responsive-grid {
            display: grid;
            gap: 12px;
            transition: all 0.3s;
        }
        .responsive-grid.cols-1 { grid-template-columns: 1fr; }
        .responsive-grid.cols-2 { grid-template-columns: 1fr 1fr; }
        .responsive-grid.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
        .grid-item {
            background: #e8f0fe;
            border-radius: 8px;
            padding: 16px;
            text-align: center;
            font-size: 13px;
            color: #1a73e8;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Resize Observer 尺寸监控</h1>
        <p class="desc">拖动右下角调整盒子大小,观察尺寸变化。</p>

        <div class="resizable-box" id="resizableBox">
            <div style="font-size:18px;font-weight:700;margin-bottom:8px;">可调整大小的盒子</div>
            <div style="font-size:13px;opacity:0.8;">拖动右下角调整尺寸</div>
            <div class="size-badge" id="sizeBadge">0 x 0</div>
        </div>

        <div class="card">
            <h2>尺寸详情</h2>
            <div class="size-info">
                <div class="size-item">
                    <div class="size-label">宽度</div>
                    <div class="size-value" id="widthVal">0</div>
                </div>
                <div class="size-item">
                    <div class="size-label">高度</div>
                    <div class="size-value" id="heightVal">0</div>
                </div>
                <div class="size-item">
                    <div class="size-label">变化次数</div>
                    <div class="size-value" id="changeCount">0</div>
                </div>
                <div class="size-item">
                    <div class="size-label">列数</div>
                    <div class="size-value" id="colCount">1</div>
                </div>
            </div>
        </div>

        <div class="card">
            <h2>响应式网格(自动调整列数)</h2>
            <div class="responsive-grid cols-1" id="responsiveGrid">
                <div class="grid-item">项目 1</div>
                <div class="grid-item">项目 2</div>
                <div class="grid-item">项目 3</div>
                <div class="grid-item">项目 4</div>
                <div class="grid-item">项目 5</div>
                <div class="grid-item">项目 6</div>
            </div>
        </div>

        <div class="card">
            <h2>变化日志</h2>
            <div class="log-area" id="logArea"></div>
        </div>
    </div>

    <script>
        let changeCount = 0;
        const box = document.getElementById('resizableBox');
        const grid = document.getElementById('responsiveGrid');

        const observer = new ResizeObserver(function (entries) {
            for (const entry of entries) {
                const width = Math.round(entry.contentRect.width);
                const height = Math.round(entry.contentRect.height);

                if (entry.target === box) {
                    changeCount++;
                    document.getElementById('widthVal').textContent = width + 'px';
                    document.getElementById('heightVal').textContent = height + 'px';
                    document.getElementById('changeCount').textContent = changeCount;
                    document.getElementById('sizeBadge').textContent = width + ' x ' + height;

                    addLog('盒子尺寸变化: ' + width + ' x ' + height);
                }

                if (entry.target === grid) {
                    let cols = 1;
                    if (width >= 450) cols = 3;
                    else if (width >= 280) cols = 2;

                    grid.className = 'responsive-grid cols-' + cols;
                    document.getElementById('colCount').textContent = cols;
                    addLog('网格列数变化: ' + cols + ' 列 (宽度: ' + width + 'px)');
                }
            }
        });

        observer.observe(box);
        observer.observe(grid);

        function addLog(message) {
            const log = document.getElementById('logArea');
            const time = new Date().toLocaleTimeString('zh-CN');
            const line = document.createElement('div');
            line.textContent = '[' + time + '] ' + message;
            log.insertBefore(line, log.firstChild);
            while (log.children.length > 50) {
                log.removeChild(log.lastChild);
            }
        }
    </script>
</body>
</html>

五、浏览器兼容性

浏览器 支持版本
Chrome 64+
Firefox 69+
Safari 13.1+
Edge 79+
Opera 51+
IE 不支持
iOS Safari 13.4+

六、注意事项与最佳实践

1. 避免无限循环

代码示例

// 在回调中修改被观察元素的尺寸可能导致循环
const observer = new ResizeObserver(entries => {
    // 不要直接修改被观察元素的尺寸
    // 如果必须修改,使用 requestAnimationFrame 延迟
    requestAnimationFrame(() => {
        // 修改尺寸
    });
});

2. 性能优化

代码示例

// 使用防抖减少回调频率
function debounce(fn, delay) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

const observer = new ResizeObserver(debounce(entries => {
    // 处理尺寸变化
}, 100));

3. 及时断开

代码示例

// 组件卸载时断开
observer.disconnect();

七、代码规范示例

代码示例

class ElementResizeWatcher {
    constructor(options = {}) {
        this.debounceMs = options.debounceMs || 0;
        this.callbacks = new Map();
        this.observer = new ResizeObserver(this._handleResize.bind(this));
    }

    _handleResize(entries) {
        entries.forEach(entry => {
            const callback = this.callbacks.get(entry.target);
            if (callback) {
                callback({
                    width: entry.contentRect.width,
                    height: entry.contentRect.height,
                    target: entry.target
                });
            }
        });
    }

    watch(element, callback) {
        this.callbacks.set(element, callback);
        this.observer.observe(element);
        return this;
    }

    unwatch(element) {
        this.callbacks.delete(element);
        this.observer.unobserve(element);
        return this;
    }

    destroy() {
        this.observer.disconnect();
        this.callbacks.clear();
    }
}

// 使用
const watcher = new ElementResizeWatcher();
watcher.watch(document.getElementById('container'), ({ width, height }) => {
    console.log('尺寸:', width, height);
});

八、常见问题与解决方案

常见问题

Q1:初始尺寸如何获取?

Resize Observer 在首次 observe 时会立即触发一次回调,可以获取初始尺寸。

Q2:如何获取包含 padding/border 的尺寸?

使用 entry.borderBoxSize[0].inlineSize/blockSizeentry.target.offsetWidth/offsetHeight

Q3:隐藏元素会触发回调吗?

元素从可见变为隐藏(display:none)时会触发一次回调(尺寸变为0),之后不再触发。


九、总结

Resize Observer 提供了监听任意元素尺寸变化的能力,比 window.resize 更灵活、更高效。通过 contentRect 和 borderBoxSize 可以获取精确的尺寸信息,适用于响应式布局、图表重绘、自适应组件等场景。使用时需注意避免在回调中修改被观察元素的尺寸(防止循环),及时断开不再需要的观察,以及合理使用防抖优化性能。

标签: ResizeObserver 元素尺寸监听 响应式布局 自适应组件 性能优化

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: HTML5 API:HTML5 Intersection Observ - 完整教程与代码示例 下一篇: HTML5 API:HTML5 Performance Observe - 完整教程与代码示例

poll相关推荐