在 Vue3 + Vite 项目中实现移动端自适应,可以通过以下方案实现(附完整配置):
方案一:Viewport 视口方案(推荐)
1. 安装核心依赖
npm install postcss-px-to-viewport -D
2. 配置 vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import postCssPx from "postcss-px-to-viewport";
export default defineConfig({
plugins: [vue()],
css: {
postcss: {
plugins: [
postCssPx({
unitToConvert: "px", // 要转换的单位
viewportWidth: 750, // 设计稿宽度
unitPrecision: 5, // 转换精度
propList: ["*"], // 需要转换的属性列表
viewportUnit: "vw", // 转换单位
fontViewportUnit: "vw", // 字体转换单位
selectorBlackList: ["ignore-"], // 忽略类名
minPixelValue: 1, // 最小转换值
mediaQuery: false, // 媒体查询中是否转换
replace: true, // 直接替换值
exclude: [/node_modules/], // 排除文件
landscape: false, // 是否处理横屏
}),
],
},
},
});
3. 基础样式重置
在 src/assets/css/reset.css
:
/* 清除默认边距 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 禁止长按链接与图片弹出菜单 */
a,
img {
-webkit-touch-callout: none;
}
/* 禁用文字缩放 */
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
/* 统一字体 */
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial;
line-height: 1.5;
}
4. 添加 viewport meta
在 index.html
的 <head>
中:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
方案二:REM 弹性布局
1. 安装依赖
npm install lib-flexible postcss-pxtorem -D
2. 配置 vite.config.ts
// vite.config.ts
import postCssPxToRem from "postcss-pxtorem";
export default defineConfig({
css: {
postcss: {
plugins: [
postCssPxToRem({
rootValue: 75, // 设计稿宽度/10(750px设计稿填75)
propList: ["*"],
selectorBlackList: ["norem"],
}),
],
},
},
});
3. 动态设置 REM
在 src/main.ts
:
import "lib-flexible/flexible"; //rem转化的工具
// 推荐使用 lib-flexible 的升级版:amfe-flexible
// import "amfe-flexible";
// 监听窗口变化
window.addEventListener("resize", () => {
if (document.documentElement.clientWidth > 750) {
document.documentElement.style.fontSize = "75px";
} else {
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + "px";
}
});
响应式布局增强技巧
1. 媒体查询补充
/* 横屏适配 */
@media screen and (orientation: landscape) {
.container {
padding: 0 10vw;
}
}
/* 超大屏幕限制 */
@media (min-width: 768px) {
body {
max-width: 768px;
margin: 0 auto !important;
}
}
2. 1px 边框解决方案
使用 PostCSS 插件:
npm install postcss-write-svg -D
配置 vite.config.ts
:
require("postcss-write-svg")({
utf8: false,
});
使用示例:
.border-1px {
border-bottom: 1px solid transparent;
background: svg(1px-border param(--color #00ff00)) repeat-x bottom;
background-size: 100% 1px;
}
最佳实践建议
- 设计稿基准:
- 二倍图设计稿使用 750px 宽度
- 单倍图设计稿使用 375px 宽度
- 开发策略:
<!-- 在组件中直接使用设计稿尺寸 -->
<div class="box" style="width: 200px; height: 100px"></div>
<!-- 编译后自动转为 26.6667vw 13.3333vw(基于375设计稿) -->
- 第三方组件库适配(以 Vant 为例):
// postcss-px-to-viewport 配置中添加
selectorBlackList: ["van-"];
设备测试技巧
- Chrome 模拟器:
// 按 F12 打开开发者工具 -> 切换设备工具栏
// 建议测试机型:iPhone SE(320px)、iPhone 12 Pro(390px)、Pixel 5(393px)
- 真机调试:
npm run build
npm install -g serve
serve -s dist
# 手机访问电脑IP:5000
两种方案各有优势:
✅ Viewport 方案:更适合现代浏览器,计算逻辑更直观,追求零脚本化;设计稿尺寸不规则或需精确视口控制;项目构建工具为 Vite(配置更简洁
✅ REM 方案:兼容性更好,适合需要支持老旧浏览器的项目;项目集成 Vant、Element 等 UI 框架;设计稿为 375px/750px 等标准尺寸
建议根据项目需求选择,通常 Viewport 方案能满足大部分移动端场景。配置完成后,开发时直接使用设计稿像素值即可实现完美自适应。
两种方性能与兼容性
指标 | postcss-pxtorem | postcss-px-to-viewport |
---|---|---|
构建耗时 | 较低(依赖 CSS 计算) | 稍高(需解析视口比例) |
运行时性能 | 需 JS 动态计算根字体 | 纯 CSS 驱动,无运行时开销 |
兼容性风险 | 低(支持老旧设备) | 需注意 Android 4.4 以下兼容 |
UI 框架适配 | 完美兼容 Vant、Element 等 | 需手动排除第三方库样式 |
核心优势 | 兼容性好、UI 框架友好 | 无脚本依赖、视口精确控制 |
劣势 | 需动态计算根字体 | 老旧设备兼容性差 |
适用项目 | 企业后台、兼容性要求高 | 现代 H5、活动页 |
配置复杂度 | 中 | 低 |
完整实例
1.安装 npm 包
npm install postcss-pxtorem -D
npm install amfe-flexible -D
# 或
npm install postcss-px-to-viewport -D
2.配置 vue.config.ts
// vue.config.ts
import { defineConfig, loadEnv } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
import postCssPxToRem from "postcss-pxtorem";
import postCssPx from "postcss-px-to-viewport";
// https://vite.dev/config/
export default defineConfig((config) => {
// 加载 .env 文件中的环境变量
const env = loadEnv(config.mode, process.cwd());
return {
base: "./",
//设置别名
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
plugins: [vue()],
esbuild: {
// 打包之后删除 console debugger
// drop: ["console", "debugger"],
},
css: {
postcss: {
plugins: [
postCssPxToRem({
rootValue: 75,
// rootValue: ({ file }) => (file.includes("vant") ? 37.5 : 75), // 适配 Vant
propList: ["*"],
selectorBlackList: ["norem"],
}),
// 二选一,若使用vant,则推荐使用 postCssPxToRem
// postCssPx({
// unitToConvert: "px", // 要转换的单位
// viewportWidth: 750, // 设计稿宽度(以iPhone6/7/8为基准)
// unitPrecision: 5, // 转换精度
// propList: ["*"], // 需要转换的属性列表
// viewportUnit: "vw", // 转换单位
// fontViewportUnit: "vw", // 字体转换单位
// selectorBlackList: ["ignore-"], // 忽略类名
// minPixelValue: 1, // 最小转换值
// mediaQuery: false, // 媒体查询中是否转换
// replace: true, // 直接替换值
// exclude: [/node_modules/], // 排除文件
// landscape: false, // 是否处理横屏
// }),
],
},
},
server: {
proxy: {
"/api": {
target: env.VITE_QAURL, // 目标服务器地址
changeOrigin: true,
prependPath: true,
secure: false,
// rewrite: (path) => path.replace(/^\/api/, ''),// 重写路径,将 /api 替换为空字符串
},
},
},
};
});
3.配置 main.ts
// main.ts
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
import "@/assets/css/reset.scss";
import Vant from "vant";
// 引入vant组件样式
import "vant/lib/index.css";
import "amfe-flexible"; //rem转化的工具
const app = createApp(App);
app.use(Vant);
app.use(createPinia());
app.use(router);
app.mount("#app");
4.重置 css 样式
/* reset.scss */
* {
margin: 0;
padding: 0;
list-style: none;
box-sizing: border-box;
text-decoration: none;
outline: none !important;
user-select: none;
}
p {
text-align: center;
font-family: Source Han Sans MicrosoftYaHei-Bold;
font-weight: normal;
font-stretch: normal;
}
/* 禁止图片复制 */
img {
user-select: none !important;
}
5.添加 viewport meta
在 index.html
的 <head>
中:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
踩坑
1.vue3 路由模式为 history,部署之后出现的白屏问题
- 配置 vue.config.ts 中的 publicPath
vue.config.ts
module.exports = {
//根目录
publicPath: process.env.NODE_ENV === "production" ? "./" : "/",
};
2.配置 vue-router
const router = createRouter({
//根目录
history: createWebHistory("/"),
routes,
});
3.配置 nginx 的 server
location / {
try_files $uri $uri/ /index.html;
}