Skip to content

useRect 📐

介绍

想要精确获取元素的位置和尺寸?想要知道元素在视口中的确切坐标?useRect 就是你的测量神器!📏

这个 Hook 为你提供了便捷的元素位置和尺寸获取功能,等价于 Element.getBoundingClientRect,让你轻松掌握元素的几何信息!

代码演示

基础用法 📍

获取元素的基本位置和尺寸信息:

html
<div ref="root" class="demo-box">
  我是一个测试元素
</div>
js
import { ref, onMounted } from 'vue';
import { useRect } from '@vant/use';

export default {
  setup() {
    const root = ref();

    onMounted(() => {
      const rect = useRect(root);
      console.log('📐 元素信息:', rect);
      console.log(`📏 宽度: ${rect.width}px`);
      console.log(`📏 高度: ${rect.height}px`);
      console.log(`📍 位置: (${rect.left}, ${rect.top})`);
    });

    return { root };
  },
};

实时位置监控 🎯

结合事件监听,实时监控元素位置变化:

html
<div ref="container" class="scroll-container">
  <div ref="target" class="target-element">
    拖拽或滚动我试试!
  </div>
</div>
js
import { ref, onMounted } from 'vue';
import { useRect } from '@vant/use';
import { useEventListener } from '@vant/use';

export default {
  setup() {
    const container = ref();
    const target = ref();

    const updatePosition = () => {
      const rect = useRect(target);
      console.log(`🎯 当前位置: x=${rect.left}, y=${rect.top}`);
      console.log(`📦 当前尺寸: ${rect.width} × ${rect.height}`);
    };

    onMounted(() => {
      // 监听滚动事件
      useEventListener('scroll', updatePosition, { target: container });
      
      // 监听窗口大小变化
      useEventListener('resize', updatePosition);
      
      // 初始位置
      updatePosition();
    });

    return {
      container,
      target,
    };
  },
};

碰撞检测 💥

检测两个元素是否发生碰撞:

html
<div ref="element1" class="box box1">元素 1</div>
<div ref="element2" class="box box2">元素 2</div>
<div class="collision-status">{{ collisionStatus }}</div>
js
import { ref, onMounted, computed } from 'vue';
import { useRect } from '@vant/use';

export default {
  setup() {
    const element1 = ref();
    const element2 = ref();
    const rect1 = ref({});
    const rect2 = ref({});

    const checkCollision = () => {
      rect1.value = useRect(element1);
      rect2.value = useRect(element2);
    };

    const collisionStatus = computed(() => {
      const r1 = rect1.value;
      const r2 = rect2.value;
      
      if (!r1.width || !r2.width) return '🔍 检测中...';
      
      const isColliding = !(
        r1.right < r2.left ||
        r1.left > r2.right ||
        r1.bottom < r2.top ||
        r1.top > r2.bottom
      );
      
      return isColliding ? '💥 发生碰撞!' : '✅ 安全距离';
    });

    onMounted(() => {
      // 定期检测碰撞
      setInterval(checkCollision, 100);
    });

    return {
      element1,
      element2,
      collisionStatus,
    };
  },
};

视口可见性检测 👁️

检测元素是否在视口中可见:

html
<div ref="target" class="lazy-element">
  {{ visibilityStatus }}
</div>
js
import { ref, onMounted, computed } from 'vue';
import { useRect } from '@vant/use';
import { useEventListener } from '@vant/use';

export default {
  setup() {
    const target = ref();
    const elementRect = ref({});

    const updateRect = () => {
      elementRect.value = useRect(target);
    };

    const visibilityStatus = computed(() => {
      const rect = elementRect.value;
      if (!rect.width) return '🔍 检测中...';

      const windowHeight = window.innerHeight;
      const windowWidth = window.innerWidth;

      const isVisible = (
        rect.top < windowHeight &&
        rect.bottom > 0 &&
        rect.left < windowWidth &&
        rect.right > 0
      );

      const visibleArea = Math.max(0, 
        Math.min(rect.bottom, windowHeight) - Math.max(rect.top, 0)
      ) * Math.max(0,
        Math.min(rect.right, windowWidth) - Math.max(rect.left, 0)
      );

      const totalArea = rect.width * rect.height;
      const visiblePercentage = totalArea > 0 ? (visibleArea / totalArea * 100).toFixed(1) : 0;

      return isVisible 
        ? `👁️ 可见 (${visiblePercentage}%)`
        : '🙈 不可见';
    });

    onMounted(() => {
      updateRect();
      useEventListener('scroll', updateRect);
      useEventListener('resize', updateRect);
    });

    return {
      target,
      visibilityStatus,
    };
  },
};

拖拽边界限制 🚧

限制元素拖拽在指定区域内:

html
<div ref="container" class="drag-container">
  <div ref="draggable" class="draggable-element">
    拖拽我!
  </div>
</div>
js
import { ref, onMounted } from 'vue';
import { useRect } from '@vant/use';

export default {
  setup() {
    const container = ref();
    const draggable = ref();
    let isDragging = false;
    let startX = 0;
    let startY = 0;

    const handleMouseDown = (e) => {
      isDragging = true;
      startX = e.clientX;
      startY = e.clientY;
    };

    const handleMouseMove = (e) => {
      if (!isDragging) return;

      const containerRect = useRect(container);
      const draggableRect = useRect(draggable);
      
      const deltaX = e.clientX - startX;
      const deltaY = e.clientY - startY;
      
      // 计算新位置
      let newLeft = draggableRect.left + deltaX - containerRect.left;
      let newTop = draggableRect.top + deltaY - containerRect.top;
      
      // 边界限制
      newLeft = Math.max(0, Math.min(newLeft, containerRect.width - draggableRect.width));
      newTop = Math.max(0, Math.min(newTop, containerRect.height - draggableRect.height));
      
      // 应用新位置
      draggable.value.style.left = `${newLeft}px`;
      draggable.value.style.top = `${newTop}px`;
      
      startX = e.clientX;
      startY = e.clientY;
      
      console.log(`🎯 拖拽位置: (${newLeft.toFixed(1)}, ${newTop.toFixed(1)})`);
    };

    const handleMouseUp = () => {
      isDragging = false;
    };

    onMounted(() => {
      draggable.value.addEventListener('mousedown', handleMouseDown);
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
    });

    return {
      container,
      draggable,
    };
  },
};

响应式布局计算 📱

根据元素尺寸动态调整布局:

html
<div ref="adaptive" class="adaptive-container">
  <div class="content">{{ layoutInfo }}</div>
</div>
js
import { ref, onMounted, computed } from 'vue';
import { useRect } from '@vant/use';
import { useEventListener } from '@vant/use';

export default {
  setup() {
    const adaptive = ref();
    const containerRect = ref({});

    const updateLayout = () => {
      containerRect.value = useRect(adaptive);
    };

    const layoutInfo = computed(() => {
      const rect = containerRect.value;
      if (!rect.width) return '📱 计算中...';

      const aspectRatio = (rect.width / rect.height).toFixed(2);
      let deviceType = '';
      
      if (rect.width < 768) {
        deviceType = '📱 移动端';
      } else if (rect.width < 1024) {
        deviceType = '📟 平板端';
      } else {
        deviceType = '💻 桌面端';
      }

      return `
        ${deviceType}
        📐 尺寸: ${rect.width} × ${rect.height}
        📏 比例: ${aspectRatio}
        📍 位置: (${rect.left}, ${rect.top})
      `;
    });

    onMounted(() => {
      updateLayout();
      useEventListener('resize', updateLayout);
    });

    return {
      adaptive,
      layoutInfo,
    };
  },
};

API 参考

类型定义

ts
function useRect(
  element: Element | Window | Ref<Element | Window | undefined>,
): DOMRect;

interface DOMRect {
  width: number;
  height: number;
  top: number;
  left: number;
  right: number;
  bottom: number;
  x: number;
  y: number;
}

参数说明

参数说明类型默认值
element目标元素或元素引用Element | Window | Ref<Element | Window | undefined>-

返回值 DOMRect

属性说明类型
width元素宽度number
height元素高度number
top顶部与视口左上角的距离number
left左侧与视口左上角的距离number
right右侧与视口左上角的距离number
bottom底部与视口左上角的距离number
x等同于 leftnumber
y等同于 topnumber

实际应用场景

🎯 交互功能

  • 拖拽边界限制
  • 碰撞检测系统
  • 悬浮提示定位
  • 上下文菜单定位

📱 响应式设计

  • 自适应布局计算
  • 断点检测
  • 元素尺寸监控
  • 设备适配

🎨 动画效果

  • 元素跟随动画
  • 视差滚动效果
  • 进入/离开动画
  • 位置过渡动画

📊 数据可视化

  • 图表元素定位
  • 标签位置计算
  • 缩放中心点计算
  • 坐标系转换

最佳实践

✅ 推荐做法

js
// 1. 在适当时机获取位置信息
onMounted(() => {
  const rect = useRect(element);
  // 确保元素已渲染
});

// 2. 结合事件监听实时更新
useEventListener('resize', () => {
  const rect = useRect(element);
  updateLayout(rect);
});

// 3. 缓存计算结果
const cachedRect = ref({});
const updateRect = debounce(() => {
  cachedRect.value = useRect(element);
}, 100);

❌ 避免做法

js
// 1. 避免频繁调用
setInterval(() => {
  const rect = useRect(element); // 性能问题
}, 16);

// 2. 避免在元素未渲染时调用
const rect = useRect(element); // 可能获取到错误信息

// 3. 避免忽略浏览器兼容性
const rect = useRect(element);
// 某些旧浏览器可能不支持所有属性

调试技巧

🔍 可视化调试

js
// 在元素上显示位置信息
const showRectInfo = (element) => {
  const rect = useRect(element);
  
  const info = document.createElement('div');
  info.style.cssText = `
    position: fixed;
    top: ${rect.top}px;
    left: ${rect.left}px;
    background: rgba(255, 0, 0, 0.8);
    color: white;
    padding: 4px;
    font-size: 12px;
    z-index: 9999;
  `;
  info.textContent = `${rect.width}×${rect.height} (${rect.left},${rect.top})`;
  
  document.body.appendChild(info);
  
  setTimeout(() => {
    document.body.removeChild(info);
  }, 2000);
};

🐛 性能监控

js
// 监控 useRect 调用性能
const performanceRect = (element) => {
  const start = performance.now();
  const rect = useRect(element);
  const end = performance.now();
  
  console.log(`📊 useRect 耗时: ${(end - start).toFixed(2)}ms`);
  return rect;
};

浏览器兼容性

浏览器版本支持
Chrome✅ 1+
Firefox✅ 3+
Safari✅ 4+
Edge✅ 12+
IE✅ 9+

相关文档

🎯 核心功能

🎨 动画相关

🔧 开发工具

📱 移动端优化

🎪 高级用法

基於Vant構建的企業級移動端解決方案