UnoCSS / Tailwind CSS 断点检测方案:零依赖配置自动同步

在 UnoCSS 或 Tailwind CSS 开发中,框架本身不提供 JavaScript API 来直接获取当前命中的断点(如 sm、md)。断点本质是 CSS @media 查询,由浏览器内核处理,无法被 JS 直接读取。

硬编码像素值或使用第三方库(如 @vueuse/core)虽可行,但需手动维护两份配置,易导致样式与逻辑不同步。本文介绍一种零依赖、配置自动同步的方案:利用 CSS 变量在不同断点下注入标识,JS 通过 ResizeObserver 实时读取。


核心原理

  1. CSS 层:利用框架的屏幕指令(UnoCSS @screen / Tailwind @media),在根元素设置不同的 CSS 变量值。
  2. JS 层:使用 getComputedStyle 读取变量,配合 ResizeObserver 监听变化。

优势:修改配置文件(uno.config.ts 或 tailwind.config.js)后,CSS 和 JS 逻辑自动同步,无需修改 JS 代码。


实现步骤

1. 定义 CSS 映射

创建 screen-mode.css。以下以 UnoCSS 为例(Tailwind 用户只需将 @screen 替换为标准 @media (min-width: ...) 即可)。

/* screen-mode.css */
.screen-mode-detector {
  --un-screen-mode: 'xs';
}

/* UnoCSS @screen 指令会自动转换为 @media (min-width: xxx) */
@screen sm {
  .screen-mode-detector { --un-screen-mode: 'sm'; }
}

@screen md {
  .screen-mode-detector { --un-screen-mode: 'md'; }
}

@screen lg {
  .screen-mode-detector { --un-screen-mode: 'lg'; }
}

@screen xl {
  .screen-mode-detector { --un-screen-mode: 'xl'; }
}

@screen 2xl {
  .screen-mode-detector { --un-screen-mode: '2xl'; }
}
Tailwind 用户注意:若使用 Tailwind,请将上述 @screen 块替换为对应的 @media (min-width: 640px) 等标准查询,数值需与 tailwind.config.js 保持一致。

2. 封装 Vue Hook

创建 useScreenMode.ts。该 Hook 采用单例模式,全局只初始化一次监听器。

import { computed, ref } from 'vue'
import './screen-mode.css'

// 全局状态,确保多次调用只创建一个 Observer
const screen = ref<string>('xs')
const support = ref<boolean>(false)
let observer: ResizeObserver | null = null

export function useScreenMode() {
  initScreenMode()
  return {
    mode: computed(() => screen.value),
    support: computed(() => support.value),
  }
}

function initScreenMode() {
  // SSR 保护
  if (typeof document === 'undefined') return
  
  const root = document.documentElement
  
  // 避免重复初始化
  if (root.classList.contains('screen-mode-detector')) {
    support.value = true
    return
  }
  
  root.classList.add('screen-mode-detector')
  
  const update = () => {
    // 必须使用 getComputedStyle 读取样式表中的变量
    const val = getComputedStyle(root).getPropertyValue('--un-screen-mode')
    screen.value = val.trim().replace(/['"]/g, '')
    support.value = true
  }

  // 初始执行
  update()

  // 使用 ResizeObserver 监听根元素尺寸变化
  // 比 window.resize 性能更好,且异步批量处理
  observer = new ResizeObserver(() => {
    update()
  })
  
  observer.observe(root)
}

3. 组件中使用

<script setup lang="ts">
import { useScreenMode } from '@/hooks/useScreenMode'

const { mode } = useScreenMode()
</script>

<template>
  <div>
    <p>当前场景: {{ mode }}</p>
    
    <!-- 根据断点渲染不同内容 -->
    <div v-if="['xs', 'sm'].includes(mode)">移动端视图</div>
    <div v-else-if="mode === 'md'">平板视图</div>
    <div v-else>桌面端视图</div>
  </div>
</template>

关键技术点解析

为什么用 getComputedStyle?

el.style.getPropertyValue() 仅能读取内联样式(style="" 属性)。而我们的变量定义在 CSS 样式表中,必须通过 window.getComputedStyle(el) 获取经过浏览器计算后的最终生效值。

为什么用 ResizeObserver?

相比 window.addEventListener('resize'),ResizeObserver 具有以下优势:

  1. 性能更优:由浏览器异步批量触发,不会阻塞主线程。
  2. 精准监听:仅当观察元素尺寸实际变化时回调。
  3. 无需防抖:虽然代码中未加防抖,但 ResizeObserver 本身的机制已比原生 resize 事件平滑得多。若追求极致,可保留简单的防抖逻辑。

SSR 兼容性

代码中加入了 typeof document === 'undefined' 判断,确保在服务端渲染(Nuxt/Next.js)时不报错。客户端挂载后,mode 会迅速更新为正确值。


方案对比

方案同步性依赖性能推荐度
CSS 变量 + Observer自动同步无⭐⭐⭐⭐⭐强烈推荐
@vueuse/core需手动配置有⭐⭐⭐⭐推荐 (Vue)
window.innerWidth需手动配置无⭐⭐⭐一般

此方案让 CSS 负责响应式判断,JS 负责读取状态,各司其职,是解决原子化 CSS 框架中 JS 感知断点问题的最佳实践。


百度AI生成,内容仅供参考