需求场景
Nuxt 3 项目,设计稿移动端是 750 像素宽,Pc 端是 1920 像素宽,要在一个项目里都跑 —— 移动端写 32px 字号自动按比例缩放,Pc 端原样显示。
核心思路:flexible.js 根据设备宽度动态设置根字号 html.style.fontSize,然后用 postcss-pxtorem 在构建期把代码里的 px 自动转 rem。这样写代码时永远写 px,运行时自动适配。

方案核心:flexible.js + postcss-pxtorem
- flexible.js —— 阿里 lib-flexible,运行时根据屏幕宽度调整
html的font-size,这样 1rem 等于 (屏幕宽 / 10) px - postcss-pxtorem —— PostCSS 插件,把所有 px 在 CSS 编译时除以一个值,转成 rem
结果就是你写 font-size: 32px;,实际跑出来根据屏幕宽度自动调整。傻瓜式,不用思考。

项目依赖
{
"name": "nuxt-app",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@types/node": "^18",
"nuxt": "^3.5.0",
"postcss": "^8.4.23",
"postcss-pxtorem": "^6.0.0"
},
"dependencies": {
"less": "^4.1.3"
}
}
nuxt.config.ts 配置 postcss
// nuxt.config.ts
export default defineNuxtConfig({
postcss: {
plugins: {
'postcss-pxtorem': {
rootValue: 75, // 设计稿宽度 / 10 (750 设计稿就 75)
propList: ['*'], // 所有属性都转
selectorBlackList: [/^.pc-/], // 类名以 .pc- 开头的不转(Pc 专用)
minPixelValue: 2, // 小于 2px 不转
exclude: /node_modules/i
}
}
}
})
核心两个参数:
rootValue: 75—— 设计稿 750px,转换基准是 75。配合 flexible.js 把屏幕分成 10 份的逻辑。selectorBlackList—— 黑名单,匹配的选择器不转 px。这是关键,后面 PC 适配靠它。
引入 flexible.js
Nuxt 3 客户端插件:
// plugins/flexible.client.ts
import 'amfe-flexible'
export default defineNuxtPlugin(() => {})
amfe-flexible 是 lib-flexible 的官方继任者(amfe = 阿里移动前端组),包名变了但功能一样。装一下:
npm install amfe-flexible
文件名结尾 .client.ts 关键:Nuxt 3 约定只在客户端执行这个插件,SSR 阶段不跑(因为服务端没 window,flexible.js 会报错)。
PC 端怎么不被转
核心技巧:Pc 端用专属 class 前缀(pc-、desktop-),postcss 黑名单跳过这些选择器。
实际例子:
<template>
<!-- 移动端布局 — 写的 px 会被转 rem,自动适配 -->
<div v-if="isMobile" class="m-page">
<h1 class="m-title">标题</h1>
</div>
<!-- Pc 端布局 — class 名 pc- 开头,px 不转 -->
<div v-else class="pc-page">
<h1 class="pc-title">Title</h1>
</div>
</template>
<style lang="less" scoped>
.m-page {
padding: 30px;
.m-title { font-size: 36px; } /* → rem,屏幕宽度自动缩放 */
}
.pc-page {
padding: 60px;
.pc-title { font-size: 48px; } /* → 保留 px,固定大小 */
}
</style>
判断当前是 PC 还是移动端
客户端的 composable:
// composables/useDevice.ts
export const useDevice = () => {
const isMobile = ref(false)
if (process.client) {
const check = () => {
isMobile.value = window.innerWidth < 768
}
check()
window.addEventListener('resize', check)
onBeforeUnmount(() => window.removeEventListener('resize', check))
}
return { isMobile }
}
更稳的方式是用 VueUse 的 useMediaQuery:
import { useMediaQuery } from '@vueuse/core'
const isMobile = useMediaQuery('(max-width: 767px)')
SSR 阶段的坑
Nuxt 是 SSR,服务器渲染时没有 window,useDevice 第一次拿到 isMobile 一定是 false。客户端水合后才更新。这会导致首屏闪烁(Pc 布局闪一下变成移动端)。
解决:用 User-Agent 在服务端判断:
// composables/useDevice.ts
export const useDevice = () => {
const headers = useRequestHeaders(['user-agent'])
const ua = headers['user-agent'] || ''
const isMobile = ref(/Mobile|Android|iPhone|iPad/i.test(ua))
// 客户端校正(防止 UA 误判)
onMounted(() => {
isMobile.value = window.innerWidth < 768
})
return { isMobile }
}
这样 SSR 时已经按 UA 判断好了,客户端水合时再用屏幕宽度做精确校正,首屏不闪。
下载示例项目
把完整代码打包了两份,直接 clone 看 / 跑:
替代方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| flexible + pxtorem | 动态根字号 + 自动转 rem | 最傻瓜,代码里只写 px | 需要黑名单分 Pc/移动 |
| vw 单位 | postcss-px-to-viewport 转 vw | 不依赖 JS,纯 CSS | 超大屏(平板横屏)字会过大 |
| UnoCSS / TailwindCSS | 原子化 class + 响应式断点 | 最现代,可控性最强 | 学习成本,改设计稿对应难 |
新项目我个人更倾向 UnoCSS / Tailwind,旧项目转 rem 的成本最低,看场景挑。
—— 别看了 · 2026
看看
这个已经更新了