华为又上新了,8月20日亮相的新机命名为 Pura X View。
这也是全球首款阔直板手机,感觉类似于之前出的阔折叠副屏放大版?

价格我倒不关心(因为我大概率买不起),屏幕尺寸却让我格外上心,这款机器屏幕尺寸是 16:9.5。
不是传统移动端设备的 3:2,或者是 16:9,甚至是 21:9。
面对层出不清的新设备,新比例,前端开发工程师,不!
是Agent全栈开发工程师,应该怎么去做适配?
方案
咱们还是先捋捋目前移动端适配的几种主流方案:
rem方案(老经典,曾经的王)
rem方案之前用的非常多,rem 相对于根元素html font‑size。
JS 根据屏幕宽度动态修改 html 的 font‑size,页面写 px,通过工具转成 rem。
早期的手淘 lib‑flexible 方案就是这个原理。
这套方案兼容性极佳,尤其是老安卓。
但因为 Js 参与了适配,容易造成 CSS 和 Js 强耦合。
flex + 百分比
"盒子"的宽高用百分比,布局用 flex 弹性伸缩。
这个方案适合"流式布局",但是如果是做内页不行,尤其固定尺寸的按钮、图标等。
所以一般也就是写"首页/首屏"用一下。
Media 媒体查询
使用 @media 根据屏幕宽度动态执行多套CSS,这个方案在很多老网站上仍然存在。
实现原理简单易懂,但是缺点很明显,一类屏宽一套样式,写起来太麻烦了。
之前做多端兼容的官网用这个很多,后来也逐渐弃用了。
initial‑scale 动态缩放
使用 JS 动态修改 meta viewport 的initial‑scale,把所有屏幕缩放到跟设计稿相同。
比如设计稿 750,设备 375,设置 scale=0.5实现完美对应。
优势很明显,只需要Js改一下缩放就行。
缺陷更明显,特殊屏幕尺寸上一坨答辩。
尤其高清屏、小屏,部分内容可能缩放的完全看不清楚。
vw/vh 方案(终极方案)
这套方案也是目前国内主流大厂都在用的移动端适配方案。
通过 postcss‑px‑to‑viewport 插件将代码中的 px 转换为 vw/vh视口单位。
绕过复杂的代码适配,在编译层面动手。
整个过程纯 CSS 实现,无 Js 侵入。
使用方法
后面咱们重点说一下这个方案的使用方法,这里咱们以 Vue3 + Vite 为例。
首先是修改 index.html,设置 meta。
html
<meta name="viewport" content="width=device‑width, initial‑scale=1, maximum‑scale=1, user‑scalable=no">
安装 postcss-px-to-viewport 插件:
bash
npm i postcss-px-to-viewport --save-dev
# 注意,Vite不能用上面这个原版,需要改用plugin版本
npm install postcss-px-to-viewport-8-plugin autoprefixer -D
配置插件转换,在根目录新建一个 postcss.config.js:
js
export default {
plugins: {
autoprefixer: {},
'postcss-px-to-viewport-8-plugin': {
unitToConvert: 'px', // 需要转换的单位
viewportWidth: 750, // UI设计稿宽度,移动端统一750
unitPrecision: 5, // 转换后vw小数精度,5足够
propList: ['*'], // 全部属性做转换
viewportUnit: 'vw',
fontViewportUnit: 'vw',
selectorBlackList: ['.ignore-vw', '.hairline'], // 加这个类名的元素不转
minPixelValue: 1, //小于 1px 的不转换,避免 0.xvw 细线异常
mediaQuery: false, // 媒体查询断点保留px,不转vw
replace: true,
exclude: [],
landscape: false
}
}
}
这里不需要修改 vite.config.js 哈,vite本身会自动读取 postcss.config.js。
到这里你就可以在代码中使用了:
css
.card {
width: 375px;
}
这里在编译完成以后就是 50vw。
但是,项目中你会发现一个问题,插件生成的有可能部分样式存在问题。
在小屏上显示的非常小,根本看不清楚。
在大屏上显示的又过大了,80的老头都能看得见。
所以这里需要给转换器加个限制:clamp() 方法。
看一下官方文档的解释:

所以,为了不让元素大的太大,小的太小,这里我们改成:
css
.card {
width: clamp(120px, 375px, 500px);
}
写起来是不是贼麻烦?所以工程上我们一般会封装一下,使用 less/scss 的 minix。
less
@mixin font‑vw($base‑px, $min‑px, $max‑px){
font‑size: clamp(#{$min‑px}, calc(#{$base‑px} / 750 * 100vw), #{$max‑px});
}
// 使用
.text {
@include font‑vw(16px,14px,18px);
}
但是新的问题出现了,每个页面都 import 一下这个 minix.less 也挺麻烦的,所以我们改用全局注入的方案。
js
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
css: {
preprocessorOptions: {
less: {
// 全局注入mixin文件,所有vue组件less环境自动生效,无需手动import
additionalData: `@import "${path.resolve(__dirname, './src/styles/mixin.less')}";`,
javascriptEnabled: true
}
}
}
})
这时候在每个 vue 页面的 style lang="less" 的地方都默认引入了 mixin.less。
所以,"终极形态"出现了。
- meta 设置视口
- 安装
postcss‑px‑to‑viewport,设置配置项 - 安装
less设置mixin.less注入 - 编写
clamp边界样式代码
工具Mixin
这里附一套我之前写的相对还比较完整的 mixin.less,项目中直接复制使用:
less
// ========== 全局适配基础变量 ==========
// 设计稿基准宽度
@design-width: 750px;
// 字体通用阈值
@font-min: 12px;
@font-max: 20px;
// 尺寸通用阈值
@size-min: 10px;
@size-max: 100%;
// 圆角通用阈值
@radius-min: 4px;
@radius-max: 20px;
// 间距通用阈值
@space-min: 4px;
@space-max: 30px;
// ========== 字体大小适配 ==========
// 字体适配:基础值、最小值、最大值
.m-font(@base, @min: @font-min, @max: @font-max) {
font-size: clamp(@min, @base, @max);
}
// 加粗字体适配
.m-font-bold(@base, @min: @font-min, @max: @font-max) {
font-size: clamp(@min, @base, @max);
font-weight: bold;
}
// 常规字号预设(项目统一规范,直接调用无需传参)
.m-font-sm() { .m-font(12px); }
.m-font-normal() { .m-font(14px); }
.m-font-base() { .m-font(16px); }
.m-font-lg() { .m-font(18px); }
.m-font-xl() { .m-font(20px); }
.m-font-xxl() { .m-font(24px, 20px, 28px); }
// ========== 宽高尺寸适配 ==========
// 宽度适配
.m-width(@base, @min: @size-min, @max: @size-max) {
width: clamp(@min, @base, @max);
}
// 高度适配
.m-height(@base, @min: @size-min, @max: @size-max) {
height: clamp(@min, @base, @max);
}
// 宽高同时适配(正方形/固定比例模块)
.m-size(@base, @min: @size-min, @max: @size-max) {
.m-width(@base, @min, @max);
.m-height(@base, @min, @max);
}
// ========== 边距适配(内外间距) ==========
// 内边距统一适配
.m-padding(@base, @min: @space-min, @max: @space-max) {
padding: clamp(@min, @base, @max);
}
// 外边距统一适配
.m-margin(@base, @min: @space-min, @max: @space-max) {
margin: clamp(@min, @base, @max);
}
// 上下内边距
.m-padding-v(@base, @min: @space-min, @max: @space-max) {
padding-top: clamp(@min, @base, @max);
padding-bottom: clamp(@min, @base, @max);
}
// 左右内边距
.m-padding-h(@base, @min: @space-min, @max: @space-max) {
padding-left: clamp(@min, @base, @max);
padding-right: clamp(@min, @base, @max);
}
// ========== 圆角适配 ==========
// 圆角适配
.m-radius(@base, @min: @radius-min, @max: @radius-max) {
border-radius: clamp(@min, @base, @max);
}
// 圆形圆角(固定适配,保证圆形不变形)
.m-radius-circle() {
border-radius: 50%;
}
// 胶囊圆角
.m-radius-pill() {
border-radius: 999px;
}
// ========== 边框、阴影适配 ==========
// 通用细线边框(解决1px适配问题)
.m-border() {
border: 1px solid #eee;
}
// 卡片阴影
.m-shadow() {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
}
// 浅色阴影
.m-shadow-light() {
box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.05);
}
// ========== 布局 ==========
// 弹性水平居中
.m-flex-center() {
display: flex;
justify-content: center;
align-items: center;
}
// 弹性左右对齐
.m-flex-between() {
display: flex;
justify-content: space-between;
align-items: center;
}
// 弹性纵向居中
.m-flex-col-center() {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
// 文本溢出省略(单行)
.m-ellipsis() {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
// 文本溢出省略(多行)
.m-ellipsis-multi(@line: 2) {
display: -webkit-box;
-webkit-line-clamp: @line;
-webkit-box-orient: vertical;
overflow: hidden;
}
// ========== 极端机型兜底 ==========
// 禁止缩放、固定尺寸(不参与vw适配)
.m-fixed() {
flex-shrink: 0 !important;
}
// 充满剩余空间
.m-flex-auto() {
flex: 1;
overflow: hidden;
}
使用实例:
css
.demo-card {
// 宽高适配
.m-width(680px);
.m-padding(30px);
.m-radius(16px);
.m-shadow();
.title {
.m-font-base();
.m-flex-between();
}
.desc {
.m-font-sm();
.m-ellipsis-multi(2);
color: #666;
}
}
这里一定要注意一个事儿,如果你使用的是 Vant UI 框架,它的设计稿基准宽度是375,不是750。
所以如果你安装了 Vant,在 postcss.config.js 这里的 viewportWidth 可以改成方法:
js
viewportWidth: (file) => {
// vant组件用375,业务代码750
return file.includes(path.join("node_modules", "vant")) ? 375 : 750;
},