pin_drop当前位置:知识文库 ❯ 图文
JS集成:HTML onresize 事 - 详细教程与实战指南
教程简介
onresize 事件在浏览器窗口大小发生变化时触发,是实现响应式布局和自适应界面的关键事件。无论是用户手动调整窗口大小、旋转移动设备,还是全屏/退出全屏操作,都会触发 resize 事件。与 scroll 事件类似,resize 事件在调整过程中也会高频触发,如果不加以优化,会导致严重的性能问题。
本教程将深入讲解 onresize 事件的核心概念、性能优化策略、防抖处理、响应式布局检测、ResizeObserver API、元素尺寸变化监听以及布局重排优化。
核心概念
onresize 事件定义
resize 事件在以下情况触发:
用户拖拽浏览器窗口边框调整大小
用户最大化/还原/最小化窗口
移动设备旋转屏幕方向
全屏/退出全屏切换
JavaScript 代码修改窗口大小
resize 事件的关键特性
获取窗口尺寸的方法
ResizeObserver API
ResizeObserver 是监听元素尺寸变化的现代 API,相比 resize 事件有以下优势:
语法与用法
HTML 属性方式
代码示例
<body onresize="handleResize()">内容</body>DOM 属性方式
代码示例
window.onresize = function() {
// 处理逻辑
};addEventListener 方式(推荐)
代码示例
window.addEventListener('resize', function() {
// 处理逻辑
});ResizeObserver 用法
代码示例
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const width = entry.contentRect.width;
const height = entry.contentRect.height;
// 处理尺寸变化
}
});
observer.observe(element);ResizeObserver 回调参数
代码示例
示例1:基础窗口大小监听
代码示例
<!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>
body {
font-family: "Microsoft YaHei", sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 30px;
}
.info-card {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
}
.info-card .label {
font-size: 13px;
color: #888;
margin-bottom: 8px;
}
.info-card .value {
font-size: 28px;
font-weight: bold;
color: #2196F3;
}
.info-card .unit {
font-size: 14px;
color: #aaa;
}
.breakpoint-bar {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.breakpoint-bar h3 { margin-top: 0; }
.bp-indicators {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.bp-tag {
padding: 6px 16px;
border-radius: 20px;
font-size: 13px;
font-weight: bold;
opacity: 0.3;
transition: opacity 0.3s;
}
.bp-tag.active { opacity: 1; }
.bp-xs { background: #ffebee; color: #c62828; }
.bp-sm { background: #fff3e0; color: #e65100; }
.bp-md { background: #fff8e1; color: #f57f17; }
.bp-lg { background: #e8f5e9; color: #2e7d32; }
.bp-xl { background: #e3f2fd; color: #1565C0; }
.size-history {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.size-history h3 { margin-top: 0; }
.history-log {
max-height: 150px;
overflow-y: auto;
font-size: 13px;
font-family: monospace;
}
</style>
</head>
<body>
<h1>窗口大小监听</h1>
<p>调整浏览器窗口大小,观察尺寸变化。</p>
<div class="info-grid">
<div class="info-card">
<div class="label">视口宽度</div>
<div class="value" id="vpWidth">0</div>
<div class="unit">px</div>
</div>
<div class="info-card">
<div class="label">视口高度</div>
<div class="value" id="vpHeight">0</div>
<div class="unit">px</div>
</div>
<div class="info-card">
<div class="label">屏幕宽度</div>
<div class="value" id="screenW">0</div>
<div class="unit">px</div>
</div>
<div class="info-card">
<div class="label">屏幕高度</div>
<div class="value" id="screenH">0</div>
<div class="unit">px</div>
</div>
<div class="info-card">
<div class="label">设备像素比</div>
<div class="value" id="dpr">0</div>
<div class="unit">x</div>
</div>
<div class="info-card">
<div class="label">方向</div>
<div class="value" id="orientation">-</div>
<div class="unit"></div>
</div>
</div>
<div class="breakpoint-bar">
<h3>当前断点</h3>
<div class="bp-indicators">
<span class="bp-tag bp-xs" id="bp-xs">XS (<576px)</span>
<span class="bp-tag bp-sm" id="bp-sm">SM (576-767px)</span>
<span class="bp-tag bp-md" id="bp-md">MD (768-991px)</span>
<span class="bp-tag bp-lg" id="bp-lg">LG (992-1199px)</span>
<span class="bp-tag bp-xl" id="bp-xl">XL (≥1200px)</span>
</div>
</div>
<div class="size-history">
<h3>尺寸变化记录</h3>
<div class="history-log" id="historyLog"></div>
</div>
<script>
const vpWidthEl = document.getElementById('vpWidth');
const vpHeightEl = document.getElementById('vpHeight');
const screenWEl = document.getElementById('screenW');
const screenHEl = document.getElementById('screenH');
const dprEl = document.getElementById('dpr');
const orientationEl = document.getElementById('orientation');
const historyLog = document.getElementById('historyLog');
// 防抖处理
let resizeTimer = null;
const DEBOUNCE_DELAY = 150;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(updateSizeInfo, DEBOUNCE_DELAY);
});
function updateSizeInfo() {
const w = window.innerWidth;
const h = window.innerHeight;
vpWidthEl.textContent = w;
vpHeightEl.textContent = h;
screenWEl.textContent = screen.width;
screenHEl.textContent = screen.height;
dprEl.textContent = window.devicePixelRatio || 1;
orientationEl.textContent = w > h ? '横屏' : '竖屏';
// 更新断点
updateBreakpoint(w);
// 记录变化
addHistory(w, h);
}
function updateBreakpoint(w) {
const bps = ['xs', 'sm', 'md', 'lg', 'xl'];
bps.forEach(bp => {
document.getElementById('bp-' + bp).classList.remove('active');
});
if (w < 576) document.getElementById('bp-xs').classList.add('active');
else if (w < 768) document.getElementById('bp-sm').classList.add('active');
else if (w < 992) document.getElementById('bp-md').classList.add('active');
else if (w < 1200) document.getElementById('bp-lg').classList.add('active');
else document.getElementById('bp-xl').classList.add('active');
}
function addHistory(w, h) {
const time = new Date().toLocaleTimeString('zh-CN', { hour12: false });
historyLog.innerHTML += `[${time}] ${w} x ${h}<br>`;
historyLog.scrollTop = historyLog.scrollHeight;
}
// 初始化
updateSizeInfo();
</script>
</body>
</html>示例2:防抖处理对比
代码示例
<!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>
body {
font-family: "Microsoft YaHei", sans-serif;
margin: 0;
padding: 20px;
background: #f5f5f5;
}
.comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.panel {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.panel h3 { margin-top: 0; }
.counter {
font-size: 48px;
font-weight: bold;
margin: 10px 0;
}
.panel:nth-child(1) .counter { color: #f44336; }
.panel:nth-child(2) .counter { color: #4CAF50; }
.desc {
font-size: 13px;
color: #666;
}
.timeline {
height: 60px;
background: #f5f5f5;
border-radius: 4px;
position: relative;
overflow: hidden;
margin-top: 10px;
}
.timeline-dot {
position: absolute;
width: 4px;
height: 100%;
background: currentColor;
opacity: 0.6;
}
.panel:nth-child(1) .timeline-dot { color: #f44336; }
.panel:nth-child(2) .timeline-dot { color: #4CAF50; }
.current-size {
font-size: 18px;
font-weight: bold;
color: #333;
}
</style>
</head>
<body>
<h1>防抖处理对比</h1>
<p>调整浏览器窗口大小,观察两种处理方式的触发次数差异。</p>
<div class="comparison">
<div class="panel">
<h3>无优化</h3>
<div class="counter" id="rawCount">0</div>
<div class="current-size" id="rawSize">0 x 0</div>
<div class="desc">每次 resize 都触发</div>
<div class="timeline" id="rawTimeline"></div>
</div>
<div class="panel">
<h3>防抖 (200ms)</h3>
<div class="counter" id="debounceCount">0</div>
<div class="current-size" id="debounceSize">0 x 0</div>
<div class="desc">停止调整 200ms 后触发</div>
<div class="timeline" id="debounceTimeline"></div>
</div>
</div>
<script>
let rawCount = 0;
let debounceCount = 0;
let debounceTimer = null;
const DEBOUNCE_DELAY = 200;
// 无优化
window.addEventListener('resize', function() {
rawCount++;
document.getElementById('rawCount').textContent = rawCount;
document.getElementById('rawSize').textContent =
window.innerWidth + ' x ' + window.innerHeight;
addDot('rawTimeline', rawCount);
});
// 防抖
window.addEventListener('resize', function() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function() {
debounceCount++;
document.getElementById('debounceCount').textContent = debounceCount;
document.getElementById('debounceSize').textContent =
window.innerWidth + ' x ' + window.innerHeight;
addDot('debounceTimeline', debounceCount);
}, DEBOUNCE_DELAY);
});
function addDot(timelineId, count) {
const timeline = document.getElementById(timelineId);
const dot = document.createElement('div');
dot.className = 'timeline-dot';
const position = Math.min((count - 1) * 8, 100);
dot.style.left = position + '%';
timeline.appendChild(dot);
// 限制点数
while (timeline.children.length > 12) {
timeline.removeChild(timeline.firstChild);
}
}
</script>
</body>
</html>示例3:响应式布局检测
代码示例
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Microsoft YaHei", sans-serif;
background: #f5f5f5;
transition: background 0.3s;
}
body.bp-xs { background: #ffebee; }
body.bp-sm { background: #fff3e0; }
body.bp-md { background: #fff8e1; }
body.bp-lg { background: #e8f5e9; }
body.bp-xl { background: #e3f2fd; }
.header {
padding: 20px;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.header h1 { font-size: 20px; }
.bp-badge {
padding: 6px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: bold;
}
.layout-demo {
padding: 20px;
max-width: 1200px;
margin: 20px auto;
}
.grid-layout {
display: grid;
gap: 16px;
transition: all 0.3s;
}
.grid-layout.cols-1 { grid-template-columns: 1fr; }
.grid-layout.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid-layout.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid-layout.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid-item {
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
text-align: center;
font-size: 16px;
font-weight: bold;
color: #333;
}
.info-bar {
max-width: 1200px;
margin: 20px auto;
padding: 15px 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.layout-info {
font-size: 14px;
color: #666;
}
.layout-info strong {
color: #2196F3;
}
</style>
</head>
<body>
<div class="header">
<h1>响应式布局检测</h1>
<span class="bp-badge" id="bpBadge">-</span>
</div>
<div class="info-bar">
<div class="layout-info">
当前断点:<strong id="currentBP">-</strong>
</div>
<div class="layout-info">
列数:<strong id="currentCols">-</strong>
</div>
<div class="layout-info">
视口:<strong id="currentSize">-</strong>
</div>
</div>
<div class="layout-demo">
<div class="grid-layout" id="gridLayout">
<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 class="grid-item">卡片 7</div>
<div class="grid-item">卡片 8</div>
</div>
</div>
<script>
const gridLayout = document.getElementById('gridLayout');
const bpBadge = document.getElementById('bpBadge');
const currentBP = document.getElementById('currentBP');
const currentCols = document.getElementById('currentCols');
const currentSize = document.getElementById('currentSize');
const breakpoints = [
{ name: 'XS', minWidth: 0, cols: 1, color: '#c62828', bg: '#ffebee' },
{ name: 'SM', minWidth: 576, cols: 2, color: '#e65100', bg: '#fff3e0' },
{ name: 'MD', minWidth: 768, cols: 2, color: '#f57f17', bg: '#fff8e1' },
{ name: 'LG', minWidth: 992, cols: 3, color: '#2e7d32', bg: '#e8f5e9' },
{ name: 'XL', minWidth: 1200, cols: 4, color: '#1565C0', bg: '#e3f2fd' }
];
let resizeTimer = null;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(updateLayout, 100);
});
function updateLayout() {
const w = window.innerWidth;
let current = breakpoints[0];
for (const bp of breakpoints) {
if (w >= bp.minWidth) {
current = bp;
}
}
// 更新网格列数
gridLayout.className = 'grid-layout cols-' + current.cols;
// 更新信息
bpBadge.textContent = current.name;
bpBadge.style.background = current.bg;
bpBadge.style.color = current.color;
currentBP.textContent = current.name;
currentCols.textContent = current.cols;
currentSize.textContent = w + 'px';
// 更新背景
document.body.className = 'bp-' + current.name.toLowerCase();
}
// 初始化
updateLayout();
</script>
</body>
</html>示例4:ResizeObserver 监听元素尺寸
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ResizeObserver 监听元素尺寸</title>
<style>
body {
font-family: "Microsoft YaHei", sans-serif;
padding: 20px;
background: #f5f5f5;
}
.demo-area {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
.resizable-box {
min-width: 100px;
min-height: 100px;
max-width: 600px;
max-height: 400px;
padding: 20px;
background: white;
border: 2px solid #2196F3;
border-radius: 12px;
resize: both;
overflow: hidden;
position: relative;
}
.resizable-box .size-label {
font-size: 14px;
color: #2196F3;
font-weight: bold;
}
.resizable-box .resize-hint {
position: absolute;
bottom: 5px;
right: 10px;
font-size: 12px;
color: #aaa;
}
.observer-log {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.observer-log h3 { margin-top: 0; }
.log-entries {
max-height: 300px;
overflow-y: auto;
font-size: 13px;
font-family: monospace;
}
.log-entry {
padding: 8px;
border-bottom: 1px solid #f0f0f0;
}
.log-entry .element-name {
font-weight: bold;
color: #2196F3;
}
.log-entry .dimensions {
color: #333;
}
.log-entry .timestamp {
color: #aaa;
font-size: 11px;
}
.flex-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.flex-item {
flex: 1;
min-width: 150px;
padding: 20px;
background: white;
border: 2px solid #4CAF50;
border-radius: 8px;
text-align: center;
}
.flex-item .size {
font-size: 18px;
font-weight: bold;
color: #4CAF50;
}
</style>
</head>
<body>
<h1>ResizeObserver 监听元素尺寸</h1>
<p>拖拽右下角调整盒子大小,观察 ResizeObserver 的回调触发。</p>
<div class="demo-area">
<div class="resizable-box" id="box1" data-name="盒子A">
<div class="size-label" id="box1Label">0 x 0</div>
<p>拖拽右下角调整大小</p>
<div class="resize-hint">⇵</div>
</div>
<div class="resizable-box" id="box2" data-name="盒子B" style="border-color: #E91E63;">
<div class="size-label" id="box2Label" style="color: #E91E63;">0 x 0</div>
<p>拖拽右下角调整大小</p>
<div class="resize-hint">⇵</div>
</div>
</div>
<h2>Flex 布局元素监听</h2>
<p>调整窗口大小,观察 flex 子元素的尺寸变化。</p>
<div class="flex-container" id="flexContainer">
<div class="flex-item" data-name="Flex-1">
<div>项目 1</div>
<div class="size" id="flex1Size">0 x 0</div>
</div>
<div class="flex-item" data-name="Flex-2">
<div>项目 2</div>
<div class="size" id="flex2Size">0 x 0</div>
</div>
<div class="flex-item" data-name="Flex-3">
<div>项目 3</div>
<div class="size" id="flex3Size">0 x 0</div>
</div>
</div>
<div class="observer-log">
<h3>ResizeObserver 日志</h3>
<div class="log-entries" id="logEntries"></div>
</div>
<script>
const logEntries = document.getElementById('logEntries');
// 创建 ResizeObserver
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const name = entry.target.dataset.name;
const width = Math.round(entry.contentRect.width);
const height = Math.round(entry.contentRect.height);
// 更新尺寸显示
if (entry.target.id === 'box1') {
document.getElementById('box1Label').textContent = width + ' x ' + height;
} else if (entry.target.id === 'box2') {
document.getElementById('box2Label').textContent = width + ' x ' + height;
}
// 更新 flex 项目尺寸
if (entry.target.classList.contains('flex-item')) {
const sizeEl = entry.target.querySelector('.size');
if (sizeEl) sizeEl.textContent = width + ' x ' + height;
}
// 记录日志
addLog(name, width, height);
}
});
// 观察可调整大小的盒子
observer.observe(document.getElementById('box1'));
observer.observe(document.getElementById('box2'));
// 观察 flex 子元素
document.querySelectorAll('.flex-item').forEach(item => {
observer.observe(item);
});
function addLog(name, width, height) {
const time = new Date().toLocaleTimeString('zh-CN', { hour12: false, fractionalSecondDigits: 1 });
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.innerHTML = `
<span class="timestamp">${time}</span>
<span class="element-name">[${name}]</span>
<span class="dimensions">${width} x ${height}</span>
`;
logEntries.insertBefore(entry, logEntries.firstChild);
// 限制日志数量
while (logEntries.children.length > 50) {
logEntries.removeChild(logEntries.lastChild);
}
}
</script>
</body>
</html>示例5:布局重排优化
代码示例
<!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>
body {
font-family: "Microsoft YaHei", sans-serif;
padding: 20px;
background: #f5f5f5;
}
.demo-section {
margin-bottom: 30px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.demo-section h3 { margin-top: 0; }
.chart-container {
position: relative;
width: 100%;
height: 200px;
background: #fafafa;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
}
.bar-chart {
display: flex;
align-items: flex-end;
justify-content: space-around;
height: 100%;
padding: 20px;
}
.bar {
flex: 1;
max-width: 60px;
background: linear-gradient(to top, #2196F3, #64B5F6);
border-radius: 4px 4px 0 0;
transition: height 0.3s ease;
position: relative;
}
.bar-label {
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
font-size: 12px;
color: #666;
white-space: nowrap;
}
.sidebar-layout {
display: flex;
gap: 20px;
min-height: 300px;
}
.sidebar {
width: 200px;
background: #e3f2fd;
border-radius: 8px;
padding: 15px;
flex-shrink: 0;
transition: width 0.3s;
}
.sidebar.collapsed {
width: 60px;
}
.sidebar h4 { margin-top: 0; font-size: 14px; }
.sidebar ul {
list-style: none;
padding: 0;
}
.sidebar li {
padding: 8px;
font-size: 13px;
cursor: pointer;
border-radius: 4px;
transition: background 0.2s;
}
.sidebar li:hover { background: rgba(33, 150, 243, 0.1); }
.main-area {
flex: 1;
background: #f5f5f5;
border-radius: 8px;
padding: 20px;
}
.toggle-btn {
padding: 8px 16px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-bottom: 10px;
}
.perf-tip {
padding: 12px 16px;
background: #fff3e0;
border-left: 4px solid #FF9800;
border-radius: 4px;
font-size: 13px;
line-height: 1.6;
margin-top: 15px;
}
</style>
</head>
<body>
<h1>布局重排优化</h1>
<div class="demo-section">
<h3>使用 CSS transform 替代布局属性变化</h3>
<p>调整窗口大小,柱状图高度使用 CSS transition 平滑过渡,避免 JavaScript 直接操作布局属性。</p>
<div class="chart-container">
<div class="bar-chart" id="barChart">
<div class="bar" style="height: 60%;"><span class="bar-label">一月</span></div>
<div class="bar" style="height: 80%;"><span class="bar-label">二月</span></div>
<div class="bar" style="height: 45%;"><span class="bar-label">三月</span></div>
<div class="bar" style="height: 90%;"><span class="bar-label">四月</span></div>
<div class="bar" style="height: 70%;"><span class="bar-label">五月</span></div>
<div class="bar" style="height: 55%;"><span class="bar-label">六月</span></div>
</div>
</div>
<div class="perf-tip">
优化提示:使用 CSS transition 和 transform 进行动画,利用 GPU 加速,避免触发 CPU 端的布局重排。
</div>
</div>
<div class="demo-section">
<h3>使用 will-change 提示浏览器优化</h3>
<button class="toggle-btn" id="toggleSidebar">切换侧边栏</button>
<div class="sidebar-layout" id="sidebarLayout">
<div class="sidebar" id="sidebar">
<h4>导航菜单</h4>
<ul>
<li>首页</li>
<li>产品</li>
<li>关于</li>
<li>联系</li>
</ul>
</div>
<div class="main-area">
<h3>主内容区域</h3>
<p>侧边栏使用 CSS transition 实现宽度变化动画,并使用 will-change 提示浏览器提前优化。</p>
</div>
</div>
<div class="perf-tip">
优化提示:为频繁变化的元素添加 will-change 属性,但不要滥用,否则会消耗额外内存。
</div>
</div>
<script>
// 侧边栏切换
const sidebar = document.getElementById('sidebar');
const toggleBtn = document.getElementById('toggleSidebar');
let isCollapsed = false;
// 使用 will-change 优化
sidebar.style.willChange = 'width';
toggleBtn.addEventListener('click', function() {
isCollapsed = !isCollapsed;
sidebar.classList.toggle('collapsed', isCollapsed);
});
// resize 时使用 requestAnimationFrame 批量更新
let resizeTicking = false;
window.addEventListener('resize', function() {
if (resizeTicking) return;
resizeTicking = true;
requestAnimationFrame(() => {
// 在这里执行需要响应窗口变化的逻辑
resizeTicking = false;
});
}, { passive: true });
</script>
</body>
</html>示例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>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Microsoft YaHei", sans-serif;
background: #0f0f23;
color: white;
min-height: 100vh;
}
.dashboard {
padding: 20px;
max-width: 1400px;
margin: 0 auto;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
.dashboard-header h1 { font-size: 24px; }
.viewport-info {
font-size: 13px;
color: #888;
background: #1a1a3e;
padding: 6px 14px;
border-radius: 20px;
}
.stats-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 20px;
}
.stat-card {
background: #1a1a3e;
border-radius: 12px;
padding: 20px;
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
}
.stat-card:nth-child(1)::before { background: #2196F3; }
.stat-card:nth-child(2)::before { background: #4CAF50; }
.stat-card:nth-child(3)::before { background: #FF9800; }
.stat-card:nth-child(4)::before { background: #E91E63; }
.stat-label {
font-size: 13px;
color: #888;
margin-bottom: 8px;
}
.stat-value {
font-size: 28px;
font-weight: bold;
}
.stat-change {
font-size: 12px;
margin-top: 5px;
}
.stat-change.up { color: #4CAF50; }
.stat-change.down { color: #f44336; }
.charts-row {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 16px;
margin-bottom: 20px;
}
.chart-card {
background: #1a1a3e;
border-radius: 12px;
padding: 20px;
}
.chart-card h3 {
margin-top: 0;
margin-bottom: 15px;
font-size: 16px;
}
.mini-chart {
height: 150px;
display: flex;
align-items: flex-end;
gap: 4px;
}
.mini-bar {
flex: 1;
background: linear-gradient(to top, #667eea, #764ba2);
border-radius: 3px 3px 0 0;
min-height: 10px;
transition: height 0.5s ease;
}
.list-items {
list-style: none;
}
.list-items li {
padding: 10px 0;
border-bottom: 1px solid #2a2a4a;
display: flex;
justify-content: space-between;
font-size: 14px;
}
.list-items li:last-child { border-bottom: none; }
/* 响应式 */
@media (max-width: 992px) {
.stats-row { grid-template-columns: repeat(2, 1fr); }
.charts-row { grid-template-columns: 1fr; }
}
@media (max-width: 576px) {
.stats-row { grid-template-columns: 1fr; }
.dashboard { padding: 10px; }
}
</style>
</head>
<body>
<div class="dashboard">
<div class="dashboard-header">
<h1>数据仪表盘</h1>
<span class="viewport-info" id="viewportInfo">0 x 0</span>
</div>
<div class="stats-row" id="statsRow">
<div class="stat-card">
<div class="stat-label">总访问量</div>
<div class="stat-value">12,847</div>
<div class="stat-change up">+12.5%</div>
</div>
<div class="stat-card">
<div class="stat-label">活跃用户</div>
<div class="stat-value">3,426</div>
<div class="stat-change up">+8.3%</div>
</div>
<div class="stat-card">
<div class="stat-label">转化率</div>
<div class="stat-value">4.7%</div>
<div class="stat-change down">-0.5%</div>
</div>
<div class="stat-card">
<div class="stat-label">收入</div>
<div class="stat-value">¥89,230</div>
<div class="stat-change up">+15.2%</div>
</div>
</div>
<div class="charts-row">
<div class="chart-card">
<h3>访问趋势</h3>
<div class="mini-chart" id="trendChart"></div>
</div>
<div class="chart-card">
<h3>热门页面</h3>
<ul class="list-items">
<li><span>首页</span><span>4,230</span></li>
<li><span>产品页</span><span>2,847</span></li>
<li><span>博客</span><span>1,923</span></li>
<li><span>关于我们</span><span>1,205</span></li>
<li><span>联系方式</span><span>876</span></li>
</ul>
</div>
</div>
</div>
<script>
const viewportInfo = document.getElementById('viewportInfo');
const trendChart = document.getElementById('trendChart');
// 生成趋势图
function generateTrendChart() {
trendChart.innerHTML = '';
const data = Array.from({ length: 20 }, () => Math.random() * 80 + 20);
data.forEach(value => {
const bar = document.createElement('div');
bar.className = 'mini-bar';
bar.style.height = value + '%';
trendChart.appendChild(bar);
});
}
generateTrendChart();
// 使用 ResizeObserver 监听仪表盘容器
const dashboard = document.querySelector('.dashboard');
let resizeTimer = null;
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const w = Math.round(entry.contentRect.width);
const h = Math.round(entry.contentRect.height);
viewportInfo.textContent = `${window.innerWidth} x ${window.innerHeight} (容器: ${w}px)`;
}
});
resizeObserver.observe(dashboard);
// 窗口 resize 时更新视口信息
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
viewportInfo.textContent = `${window.innerWidth} x ${window.innerHeight}`;
}, 100);
});
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 始终使用防抖处理
resize 事件在调整窗口过程中高频触发,必须使用防抖处理。
代码示例
let resizeTimer;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(handleResize, 200);
});2. 优先使用 CSS 媒体查询
对于纯视觉的响应式变化,优先使用 CSS 媒体查询而非 JavaScript resize 事件。
代码示例
/* 推荐:CSS 方式 */
@media (max-width: 768px) {
.sidebar { display: none; }
}
/* 不推荐:JavaScript 方式 */
// window.addEventListener('resize', function() {
// if (window.innerWidth < 768) sidebar.style.display = 'none';
// });3. 使用 matchMedia 替代 resize 断点检测
当需要在 JavaScript 中响应特定断点时,使用 matchMedia 比 resize 事件更高效。
代码示例
const mq = window.matchMedia('(max-width: 768px)');
mq.addEventListener('change', function(e) {
if (e.matches) {
// 小屏幕逻辑
} else {
// 大屏幕逻辑
}
});4. 使用 ResizeObserver 监听元素尺寸
当需要监听特定元素的尺寸变化时,使用 ResizeObserver 而非在 resize 事件中手动计算。
代码示例
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
// 处理尺寸变化
}
});
observer.observe(targetElement);5. 避免在 resize 回调中触发布局重排
不要在 resize 回调中读取 offsetWidth、getBoundingClientRect() 等触发布局计算的属性,应缓存这些值。
6. 使用 will-change 优化频繁变化的元素
对于 resize 过程中频繁变化的元素,可以添加 will-change 提示浏览器优化。
代码示例
.resizable-element {
will-change: width, height;
}但注意不要滥用,只在确实需要的元素上使用。
代码规范示例
规范的响应式处理模块
代码示例
class ResponsiveHandler {
constructor(options = {}) {
this.debounceDelay = options.debounceDelay || 200;
this.breakpoints = options.breakpoints || {
xs: 0, sm: 576, md: 768, lg: 992, xl: 1200
};
this.currentBreakpoint = null;
this.callbacks = {};
this.resizeTimer = null;
this.boundHandleResize = this._handleResize.bind(this);
}
start() {
window.addEventListener('resize', this.boundHandleResize);
this._handleResize(); // 初始化
return this;
}
stop() {
window.removeEventListener('resize', this.boundHandleResize);
clearTimeout(this.resizeTimer);
return this;
}
on(breakpoint, callback) {
if (!this.callbacks[breakpoint]) {
this.callbacks[breakpoint] = [];
}
this.callbacks[breakpoint].push(callback);
return this;
}
_handleResize() {
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
const bp = this._getCurrentBreakpoint();
if (bp !== this.currentBreakpoint) {
const prev = this.currentBreakpoint;
this.currentBreakpoint = bp;
this._triggerCallbacks(bp, prev);
}
}, this.debounceDelay);
}
_getCurrentBreakpoint() {
const w = window.innerWidth;
let current = 'xs';
for (const [name, minWidth] of Object.entries(this.breakpoints)) {
if (w >= minWidth) current = name;
}
return current;
}
_triggerCallbacks(current, previous) {
const callbacks = this.callbacks[current] || [];
callbacks.forEach(cb => cb(current, previous));
}
getCurrentBreakpoint() {
return this.currentBreakpoint;
}
is(breakpoint) {
return this.currentBreakpoint === breakpoint;
}
isAbove(breakpoint) {
return window.innerWidth >= this.breakpoints[breakpoint];
}
isBelow(breakpoint) {
return window.innerWidth < this.breakpoints[breakpoint];
}
}常见问题与解决方案
问题1:resize 事件导致页面卡顿
原因:resize 事件高频触发,处理程序中执行了 DOM 操作或布局读取。
解决方案:使用防抖处理,并避免在回调中触发布局重排。
问题2:移动端旋转屏幕时布局异常
原因:移动端旋转时 resize 事件触发时机不一致,部分浏览器在旋转动画完成前触发。
解决方案:使用 orientationchange 事件配合 matchMedia,或增加防抖延迟。
代码示例
const mq = window.matchMedia('(orientation: portrait)');
mq.addEventListener('change', function() {
setTimeout(handleOrientationChange, 300);
});问题3:ResizeObserver 回调中修改被观察元素的尺寸导致循环
原因:在 ResizeObserver 回调中修改被观察元素的尺寸,会再次触发回调。
解决方案:在回调中检查尺寸是否真的变化,或使用 requestAnimationFrame 延迟修改。
代码示例
const observer = new ResizeObserver(entries => {
requestAnimationFrame(() => {
for (const entry of entries) {
const { width } = entry.contentRect;
// 修改元素尺寸
}
});
});问题4:IE 不支持 ResizeObserver
解决方案:使用 polyfill 或回退到 resize 事件 + MutationObserver。
代码示例
// 检测支持
if (typeof ResizeObserver !== 'undefined') {
const observer = new ResizeObserver(callback);
observer.observe(element);
} else {
// 回退方案
window.addEventListener('resize', debounce(callback, 200));
}问题5:resize 与 CSS 媒体查询不同步
原因:JavaScript 的 resize 事件和 CSS 媒体查询使用不同的触发机制。
解决方案:使用 matchMedia 确保 JavaScript 和 CSS 使用相同的断点逻辑。
代码示例
const mq = window.matchMedia('(max-width: 768px)');
function handleMQChange(e) {
document.body.classList.toggle('mobile', e.matches);
}
mq.addEventListener('change', handleMQChange);
handleMQChange(mq);总结
onresize 事件是实现响应式布局的重要工具,但其高频触发特性需要谨慎处理。以下是核心要点:
防抖处理:始终对 resize 事件使用防抖处理,避免高频触发导致的性能问题。
CSS 优先:对于纯视觉的响应式变化,优先使用 CSS 媒体查询而非 JavaScript。
matchMedia:在 JavaScript 中响应断点变化时,使用
matchMedia比resize事件更高效。ResizeObserver:监听元素尺寸变化时,优先使用
ResizeObserver,它提供了更精确的尺寸信息和更好的性能。避免重排:不在 resize 回调中读取触发布局计算的属性,缓存布局信息。
will-change:对频繁变化的元素使用
will-change提示浏览器优化,但不要滥用。移动端适配:注意移动端旋转屏幕的特殊处理,使用
orientationchange和matchMedia配合。兼容性:为不支持 ResizeObserver 的浏览器提供回退方案。
正确使用 resize 事件和相关 API,能够在保证性能的前提下实现流畅的响应式体验。
常见问题
问题1:resize 事件导致页面卡顿?
resize 事件高频触发,处理程序中执行了 DOM 操作或布局读取。 使用防抖处理,并避免在回调中触发布局重排。
问题2:移动端旋转屏幕时布局异常?
移动端旋转时 resize 事件触发时机不一致,部分浏览器在旋转动画完成前触发。 使用 orientationchange 事件配合 matchMedia,或增加防抖延迟。
问题3:ResizeObserver 回调中修改被观察元素的尺寸导致循环?
在 ResizeObserver 回调中修改被观察元素的尺寸,会再次触发回调。 在回调中检查尺寸是否真的变化,或使用 requestAnimationFrame 延迟修改。
问题4:IE 不支持 ResizeObserver?
使用 polyfill 或回退到 resize 事件 + MutationObserver。
问题5:resize 与 CSS 媒体查询不同步?
JavaScript 的 resize 事件和 CSS 媒体查询使用不同的触发机制。 使用 matchMedia 确保 JavaScript 和 CSS 使用相同的断点逻辑。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别