Vue使用ResizeObserver輕松監(jiān)聽(tīng)元素尺寸變化
什么是ResizeObserver?
在前端開(kāi)發(fā)中,我們經(jīng)常需要知道一個(gè)元素的尺寸是否發(fā)生了變化。過(guò)去,我們只能通過(guò)監(jiān)聽(tīng)window的resize事件,但這只能監(jiān)聽(tīng)瀏覽器窗口的變化,無(wú)法監(jiān)聽(tīng)具體元素的變化。ResizeObserver應(yīng)運(yùn)而生,它可以幫助我們監(jiān)聽(tīng)任意元素的大小變化。
簡(jiǎn)單來(lái)說(shuō),ResizeObserver就像是一個(gè)"尺寸監(jiān)視器",當(dāng)被觀察的元素尺寸發(fā)生變化時(shí),它會(huì)立即通知我們。
為什么需要ResizeObserver?
在ResizeObserver出現(xiàn)之前,我們想要監(jiān)聽(tīng)元素尺寸變化通常有以下幾種方法:
- window.resize事件:只能監(jiān)聽(tīng)窗口變化,不能監(jiān)聽(tīng)具體元素
- 輪詢檢查:通過(guò)定時(shí)器不斷檢查元素尺寸,性能差
- MutationObserver:可以監(jiān)聽(tīng)DOM變化,但無(wú)法直接監(jiān)聽(tīng)尺寸變化
這些方法要么功能有限,要么性能不佳。ResizeObserver專門為解決這個(gè)問(wèn)題而生,它提供了高效、精準(zhǔn)的元素尺寸監(jiān)聽(tīng)能力。
基本使用方法
創(chuàng)建ResizeObserver
// 創(chuàng)建ResizeObserver實(shí)例
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
// entry.target:被觀察的元素
// entry.contentRect:元素的尺寸信息
console.log('元素尺寸發(fā)生變化:', entry.contentRect);
}
});
觀察元素
const element = document.getElementById('myElement');
resizeObserver.observe(element);
停止觀察
// 停止觀察特定元素 resizeObserver.unobserve(element); // 停止所有觀察并銷毀實(shí)例 resizeObserver.disconnect();
完整示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ResizeObserver</title>
</head>
<style>
.box {
display: flex;
gap: 20px;
}
.resizeable {
width: 300px;
height: 200px;
resize: both;
border: 1px solid #ccc;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
}
.resizeable1 {
background-color: aquamarine;
}
.resizeable2 {
background-color: blueviolet;
}
.log {
width: 300px;
height: 200px;
resize: both;
border: 1px solid #ccc;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}
</style>
<body>
<div class="box">
<div class="resizeable resizeable1" id="resizeable1">box1</div>
<div class="resizeable resizeable2" id="resizeable2">box2</div>
<div class="log">
<div class="text1"></div>
<div class="text2"></div>
</div>
</div>
</body>
<script>
const textNode1 = document.querySelector(".text1");
const textNode2 = document.querySelector(".text2");
const resizeNode1 = document.querySelector("#resizeable1");
const resizeNode2 = document.querySelector("#resizeable2");
const nodeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
console.log(entry);
const { width, height } = entry.contentRect;
if (entry.target.id === "resizeable1") {
textNode1.textContent = `box1當(dāng)前尺寸:${Math.round(
width
)} * ${Math.round(height)} 像素`;
} else {
textNode2.textContent = `box2當(dāng)前尺寸:${Math.round(
width
)} * ${Math.round(height)} 像素`;
}
}
});
nodeObserver.observe(resizeNode1);
nodeObserver.observe(resizeNode2);
</script>
</html>

在Vue 3中的使用實(shí)踐
組合式API用法
<template>
<div class="resize-observer">
<div ref="resizableElement" class="resizable-box">
<p>ResizeObserver 簡(jiǎn)單示例</p>
<p>當(dāng)前寬度: {{ width }}px</p>
<p>當(dāng)前高度: {{ height }}px</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from "vue";
const resizableElement = ref(null);
const width = ref(0);
const height = ref(0);
let resizeObserver: ResizeObserver | null = null;
onMounted(() => {
if (resizableElement.value) {
resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
width.value = entry.contentRect.width;
height.value = entry.contentRect.height;
}
});
resizeObserver.observe(resizableElement.value);
}
});
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
}
});
</script>
<style lang="scss" scoped>
.resizable-box {
resize: both;
overflow: auto;
border: 1px solid #e0e0e0;
padding: 20px;
min-width: 200px;
min-height: 100px;
background-color: #f5f5f5;
}
</style>
自定義Hook封裝
為了更好地復(fù)用,我們可以將ResizeObserver封裝成自定義Hook:
import { onUnmounted, ref } from "vue";
export function useResizeObserver() {
const width = ref(0);
const height = ref(0);
let resizeObserver: ResizeObserver | null = null;
const observe = (el: HTMLElement) => {
if (resizeObserver) {
resizeObserver.disconnect();
}
resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
width.value = entry.contentRect.width;
height.value = entry.contentRect.height;
}
});
resizeObserver.observe(el);
};
const unobserve = () => {
if (resizeObserver) {
resizeObserver.disconnect();
}
};
onUnmounted(() => {
if (resizeObserver) {
resizeObserver.disconnect();
}
});
return {
width,
height,
observe,
unobserve,
};
}
使用自定義Hook
<template>
<div class="resize-observer">
<div ref="resizableElement" class="resizable-box">
<p>使用鉤子函數(shù)實(shí)現(xiàn)的 ResizeObserver</p>
<p>當(dāng)前寬度: {{ width }}px</p>
<p>當(dāng)前高度: {{ height }}px</p>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref } from "vue";
import { useResizeObserver } from "@/hook/useResizeObserver.ts";
const { width, height, observe, unobserve } = useResizeObserver();
const resizableElement = ref(null);
onMounted(() => {
if (resizableElement.value) {
observe(resizableElement.value);
}
});
onUnmounted(() => {
if (resizableElement.value) {
unobserve(resizableElement.value);
}
});
</script>
<style lang="scss" scoped>
.resizable-box {
resize: both;
overflow: auto;
border: 1px solid #e0e0e0;
padding: 20px;
min-width: 200px;
min-height: 100px;
background-color: aquamarine;
}
</style>
Vue 3中ResizeObserver的實(shí)際應(yīng)用:可調(diào)整布局的管理后臺(tái)
<template>
<div class="demo-container">
<!-- 可調(diào)整寬度的側(cè)邊欄 -->
<aside
ref="sidebarRef"
class="sidebar"
:style="{ width: sidebarWidth + 'px' }"
>
<div class="sidebar-content">
<h3>導(dǎo)航菜單</h3>
<nav>
<a
v-for="item in menuItems"
:key="item.id"
class="nav-item"
:class="{ active: activeMenu === item.id }"
@click="activeMenu = item.id"
>
{{ item.name }}
</a>
</nav>
</div>
<!-- 拖拽把手 -->
<div class="resize-handle" @mousedown="startResize"></div>
</aside>
<main ref="mainRef" class="main-content">
<div class="content-header">
<button @click="toggleSidebar" class="toggle-btn">
{{ isSidebarCollapsed ? "展開(kāi)側(cè)邊欄" : "折疊側(cè)邊欄" }}
</button>
<h2>主要內(nèi)容區(qū)域</h2>
</div>
<!-- 顯示當(dāng)前尺寸信息 -->
<div class="size-info">
<p>側(cè)邊欄寬度: {{ sidebarWidth }}px</p>
<p>主內(nèi)容區(qū)域?qū)挾? {{ mainContentWidth }}px</p>
</div>
</main>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from "vue";
// 側(cè)邊欄狀態(tài)
const sidebarWidth = ref(280);
const isSidebarCollapsed = ref(false);
const activeMenu = ref("dashboard");
// 元素引用
const sidebarRef = ref(null);
const mainRef = ref(null);
// 尺寸數(shù)據(jù)
const mainContentWidth = ref(0);
// 菜單數(shù)據(jù)
const menuItems = [
{ id: "dashboard", name: "儀表盤" },
{ id: "users", name: "用戶管理" },
{ id: "orders", name: "訂單管理" },
{ id: "settings", name: "系統(tǒng)設(shè)置" },
];
// 監(jiān)聽(tīng)主內(nèi)容區(qū)域?qū)挾茸兓?
let mainResizeObserver: ResizeObserver | null = null;
onMounted(() => {
// 監(jiān)聽(tīng)主內(nèi)容區(qū)域?qū)挾?
mainResizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
mainContentWidth.value = entry.contentRect.width;
}
});
if (mainRef.value) {
mainResizeObserver.observe(mainRef.value);
}
});
// 切換側(cè)邊欄顯示/隱藏
const toggleSidebar = () => {
isSidebarCollapsed.value = !isSidebarCollapsed.value;
sidebarWidth.value = isSidebarCollapsed.value ? 0 : 280;
};
// 開(kāi)始調(diào)整側(cè)邊欄寬度
const startResize = (e: MouseEvent) => {
e.preventDefault();
const startX = e.clientX;
const startWidth = sidebarWidth.value;
const handleMouseMove = (e: MouseEvent) => {
const newWidth = startWidth + (e.clientX - startX);
// 限制最小和最大寬度
if (newWidth >= 200 && newWidth <= 500) {
sidebarWidth.value = newWidth;
isSidebarCollapsed.value = false;
}
};
const handleMouseUp = () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
};
onUnmounted(() => {
if (mainResizeObserver) {
mainResizeObserver.disconnect();
}
});
</script>
<style scoped>
.demo-container {
display: flex;
height: 100vh;
font-family: Arial, sans-serif;
}
.sidebar {
position: relative;
background: #2c3e50;
color: white;
transition: width 0.3s ease;
min-width: 0;
overflow: hidden;
}
.sidebar-content {
padding: 20px;
}
.sidebar h3 {
margin-bottom: 20px;
border-bottom: 1px solid #34495e;
padding-bottom: 10px;
}
.nav-item {
display: block;
padding: 10px 15px;
margin: 5px 0;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.nav-item:hover {
background: #34495e;
}
.nav-item.active {
background: #3498db;
}
.resize-handle {
position: absolute;
right: 0;
top: 0;
width: 4px;
height: 100%;
background: #34495e;
cursor: col-resize;
transition: background 0.3s;
}
.resize-handle:hover {
background: #3498db;
}
.main-content {
flex: 1;
padding: 20px;
background: #ecf0f1;
overflow-y: auto;
}
.content-header {
display: flex;
align-items: center;
margin-bottom: 20px;
gap: 15px;
}
.toggle-btn {
padding: 8px 16px;
background: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.toggle-btn:hover {
background: #2980b9;
}
.size-info {
background: white;
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.chart-container,
.table-container {
background: white;
padding: 20px;
border-radius: 4px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
</style>
以上就是Vue使用ResizeObserver輕松監(jiān)聽(tīng)元素尺寸變化的詳細(xì)內(nèi)容,更多關(guān)于Vue ResizeObserver監(jiān)聽(tīng)元素變化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
腳手架(vue-cli)創(chuàng)建Vue項(xiàng)目的超詳細(xì)過(guò)程記錄
用vue-cli腳手架可以快速的構(gòu)建出一個(gè)前端vue框架的項(xiàng)目結(jié)構(gòu),下面這篇文章主要給大家介紹了關(guān)于腳手架(vue-cli)創(chuàng)建Vue項(xiàng)目的超詳細(xì)過(guò)程,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
Vue3之toRefs和toRef在reactive中的一些應(yīng)用方式
這篇文章主要介紹了Vue3之toRefs和toRef在reactive中的一些應(yīng)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
vxe-table動(dòng)態(tài)列篩選以及篩選項(xiàng)動(dòng)態(tài)變化的問(wèn)題及解決
這篇文章主要介紹了vxe-table動(dòng)態(tài)列篩選以及篩選項(xiàng)動(dòng)態(tài)變化的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
Vue使用el-input自動(dòng)獲取焦點(diǎn)和二次獲取焦點(diǎn)問(wèn)題及解決
這篇文章主要介紹了Vue使用el-input自動(dòng)獲取焦點(diǎn)和二次獲取焦點(diǎn)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
Vue路由傳參頁(yè)面刷新后參數(shù)丟失原因和解決辦法
這幾天在開(kāi)發(fā)中遇見(jiàn)的一個(gè)關(guān)于路由傳參后,頁(yè)面刷新數(shù)據(jù)丟失的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Vue路由傳參頁(yè)面刷新后參數(shù)丟失原因和解決辦法,需要的朋友可以參考下2022-12-12
Vue使用extend動(dòng)態(tài)創(chuàng)建組件的實(shí)現(xiàn)
本文主要介紹了Vue使用extend動(dòng)態(tài)創(chuàng)建組件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
在Vue2項(xiàng)目中使用Mock.js的詳細(xì)教程
Mock.js?是一個(gè)用于生成隨機(jī)數(shù)據(jù)和攔截?Ajax?請(qǐng)求的?JavaScript?庫(kù),它非常適合在前端開(kāi)發(fā)中模擬后端?API,尤其是在前后端分離的開(kāi)發(fā)模式下,本文給大家介紹了如何在Vue2項(xiàng)目中使用Mock.js,需要的朋友可以參考下2024-10-10
vue?openlayers實(shí)現(xiàn)臺(tái)風(fēng)軌跡示例詳解
這篇文章主要為大家介紹了vue?openlayers實(shí)現(xiàn)臺(tái)風(fēng)軌跡示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11

