在做后台管理系统或者混合类型的项目时,经常会遇到这样一个需求:大部分页面按照固定像素布局就能满足要求,但其中某个大屏可视化页面、数据展示页需要根据浏览器窗口大小自动缩放,保证在任意分辨率下都不错位。如果直接在全项目启用rem布局,其他页面的样式会被连累,改动成本太高。其实Vue3配合Vite完全可以做到只让单个路由页面走px转rem的转换逻辑,其他页面保持原样。下面详细介绍具体的实现方式。

一、为什么不能直接全局配置postcss-pxtorem
很多同学一上来就在vite.config.js里全局配置postcss-pxtorem,把整个项目的css编译规则都改掉。这种做法的问题在于,postcss是构建阶段的处理器,它的作用范围是整个项目所有经过编译的样式文件,包括各个组件库的样式、公共样式文件都会被转换。一旦全局开启,那些本来按px精确还原设计稿的页面,在html根字号被动态修改后就会出现整体缩放的连锁反应。
举例来说,Element Plus的组件尺寸都是按px设计的,如果它们的样式也被转成了rem,那么当你在某个大屏页面把根字号改成设计稿对应的值时,其他页面的按钮、输入框尺寸全部会跟着变化,页面布局直接乱掉。所以核心思路应该是:postcss转换可以全局存在,但必须通过配置把不需要转换的文件排除掉;或者反过来,只让目标页面的样式参与转换。
postcss-pxtorem本身提供了exclude和include两个配置项,可以基于文件路径做正则匹配。我们可以约定把需要自适应的那个页面组件放在特定目录下,比如src/views/screen,然后只在配置里include这个目录,其他所有文件原样输出。这样就从构建层面精准圈定了转换范围。
二、安装依赖并配置Vite
首先安装必要的依赖包。postcss-pxtorem负责在构建阶段把px编译成rem,而根字号的动态计算可以自己写一段简短代码实现,不一定非要引入amfe-flexible,自己实现的好处是可以在页面离开时方便地还原根字号,避免污染其他页面。
npm install postcss-pxtorem -D
接着修改vite.config.js,在css.postcss配置中添加插件,并用include限定只处理大屏页面目录下的样式:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import pxtorem from 'postcss-pxtorem'
export default defineConfig({
plugins: [vue()],
css: {
postcss: {
plugins: [
pxtorem({
rootValue: 192, // 设计稿宽度1920,分成10份,每份192px
unitPrecision: 5,
propList: ['*'],
selectorBlackList: ['.no-rem'], // 类名带no-rem的不转换
replace: true,
mediaQuery: false,
minPixelValue: 0,
// 关键:只转换大屏页面目录下的样式
exclude: file => file && !/src[\\/]views[\\/]screen/.test(file)
})
]
}
}
})这里的exclude是一个函数,返回true表示排除该文件。上面的写法含义是:如果文件路径中不包含src/views/screen,就排除掉不转换。注意Windows下路径分隔符是反斜杠,正则里要写成[\\/]的形式来兼容两种系统。
rootValue的取值取决于你的设计稿。假设设计稿宽度是1920px,把根字号定为设计稿的十分之一即192px,那么一个设计稿上384px宽的元素转换后就是2rem。之后只需要在运行时保证html的font-size始终等于实际屏幕宽度的十分之一,页面就会等比缩放。
三、通过路由钩子动态设置根字号
构建阶段的转换做好后,还需要在运行时动态计算根字号,并且只在进入大屏页面时生效,离开时恢复默认值。这一步可以在路由的全局守卫里完成,代码集中且不影响组件本身的逻辑。
先写一个工具函数,负责设置和还原根字号,同时监听窗口尺寸变化:
// src/utils/rem.js
let resizeHandler = null
// 设计稿宽度
const DESIGN_WIDTH = 1920
export function enableRem() {
const setRootFontSize = () => {
const width = document.documentElement.clientWidth
// 屏幕宽度除以10,与rootValue的十分之一对应
document.documentElement.style.fontSize = width / 10 + 'px'
}
setRootFontSize()
resizeHandler = () => setRootFontSize()
window.addEventListener('resize', resizeHandler)
}
export function disableRem() {
if (resizeHandler) {
window.removeEventListener('resize', resizeHandler)
resizeHandler = null
}
// 还原根字号,避免影响其他页面
document.documentElement.style.fontSize = ''
}然后在路由配置和守卫中使用:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { enableRem, disableRem } from '@/utils/rem'
const routes = [
{ path: '/', component: () => import('@/views/home.vue') },
{
path: '/screen',
// 大屏页面组件放在 screen 目录下,样式才会被转换
component: () => import('@/views/screen/index.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
if (to.path === '/screen') {
enableRem()
} else {
disableRem()
}
next()
})
export default router这样处理之后,只有访问/screen这个路由时html根字号才会被改写并跟随窗口变化,切到其他页面时监听器被移除、根字号恢复,其他页面完全感知不到rem的存在。相比全局引入amfe-flexible,这种方式对项目的侵入性几乎为零。
四、常见坑与细节优化
第一个常见的坑是组件的引入位置。如果大屏页面引用的子组件没有放在screen目录下,它们的样式不会被转换,页面上就会出现一部分元素缩放、一部分不缩放的混乱情况。所以建议把大屏页面相关的所有组件、样式都集中放在同一个目录里,目录名与exclude正则保持一致。
第二个坑是第三方库样式。如果大屏页面里用了图表库,比如ECharts,图表内部是通过canvas绘制的,不经过css编译,需要在窗口resize时手动调用chart.resize()更新画布尺寸。可以在组件里监听窗口事件,也可以通过父组件派发自定义事件通知图表组件重新计算。
第三个细节是极端屏幕的处理。如果用户把浏览器窗口缩到特别窄,字号会变得非常小,文字可能看不清。可以在设置根字号时加上上下限:
const setRootFontSize = () => {
let width = document.documentElement.clientWidth
// 限制最小1200,最大2560,超出范围按边界值处理
width = Math.max(1200, Math.min(2560, width))
document.documentElement.style.fontSize = width / 10 + 'px'
}最后一个建议是给个别不需要缩放的元素保留逃生口。配置里的selectorBlackList: ['.no-rem']已经实现了这一点,当你希望某个元素始终保持固定大小时,给它加上no-rem类名即可,插件会跳过它的样式转换。这个技巧在处理边框、阴影这类不希望跟随缩放的细节时特别实用。
总结一下整套方案:构建阶段用postcss-pxtorem的exclude配置把转换范围锁定在指定目录,运行阶段用路由守卫控制根字号的启用与还原,两者配合就能实现单页面的rem自适应,同时对项目其他部分零影响。如果你的项目里也有类似的局部自适应需求,按这个思路改造即可,整体改动量很小,而且维护起来非常清晰。
Vue3rem自适应postcss-pxtorem修改时间:2026-09-05 16:38:53