UnoCSS / Tailwind CSS 断点检测方案:零依赖配置自动同步
在 UnoCSS 或 Tailwind CSS 开发中,框架本身不提供 JavaScript API 来直接获取当前命中的断点(如 sm、md)。断点本质是 CSS @media 查询,由浏览器内核处理,无法被 JS 直接读取。
硬编码像素值或使用第三方库(如 @vueuse/core)虽可行,但需手动维护两份配置,易导致样式与逻辑不同步。本文介绍一种零依赖、配置自动同步的方案:利用 CSS 变量在不同断点下注入标识,JS 通过 ResizeObserver 实时读取。
核心原理
- CSS 层:利用框架的屏幕指令(UnoCSS
@screen/ Tailwind@media),在根元素设置不同的 CSS 变量值。 - 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 具有以下优势:
- 性能更优:由浏览器异步批量触发,不会阻塞主线程。
- 精准监听:仅当观察元素尺寸实际变化时回调。
- 无需防抖:虽然代码中未加防抖,但
ResizeObserver本身的机制已比原生resize事件平滑得多。若追求极致,可保留简单的防抖逻辑。
SSR 兼容性
代码中加入了 typeof document === 'undefined' 判断,确保在服务端渲染(Nuxt/Next.js)时不报错。客户端挂载后,mode 会迅速更新为正确值。
方案对比
| 方案 | 同步性 | 依赖 | 性能 | 推荐度 |
|---|---|---|---|---|
| CSS 变量 + Observer | 自动同步 | 无 | ⭐⭐⭐⭐⭐ | 强烈推荐 |
| @vueuse/core | 需手动配置 | 有 | ⭐⭐⭐⭐ | 推荐 (Vue) |
| window.innerWidth | 需手动配置 | 无 | ⭐⭐⭐ | 一般 |
此方案让 CSS 负责响应式判断,JS 负责读取状态,各司其职,是解决原子化 CSS 框架中 JS 感知断点问题的最佳实践。
评论