问题原因
- 移动端浏览器的特殊性
- 在移动端浏览器中,100vw 包含了滚动条的宽度
- window.innerWidth 不包含滚动条宽度
- 某些 App 内的 WebView 可能会对视口进行自定义处理
- App 内 WebView 的特殊处理
- 一些 App 会修改 viewport 的 meta 设置
- WebView 可能会添加自己的导航栏或工具栏
- 部分 App 会对 WebView 进行缩放处理
解决方案
- 使用 CSS 变量动态设置视口宽度
/**
* 视口宽度管理工具
*/
export class ViewportManager {
constructor() {
this.viewportWidth = 0;
this.init();
}
init() {
// 初始化视口宽度
this.setViewportWidth();
// 监听resize事件
window.addEventListener('resize', this.handleResize.bind(this));
}
setViewportWidth() {
// 获取真实视口宽度
const realWidth = window.innerWidth;
this.viewportWidth = realWidth;
// 设置CSS变量
document.documentElement.style.setProperty('--vw', `${realWidth / 100}px`);
}
handleResize() {
this.setViewportWidth();
}
// 获取当前视口宽度
getViewportWidth() {
return this.viewportWidth;
}
}
- 创建适配工具函数
/**
* 移动端适配工具类
*/
export class MobileAdapter {
constructor(designWidth = 750) {
this.designWidth = designWidth;
this.viewportManager = new ViewportManager();
}
/**
* px 转换为 vw 单位
* @param {number} px - 像素值
* @returns {string} vw值
*/
px2vw(px) {
return `${(px / this.designWidth) * 100}vw`;
}
/**
* 获取实际像素值
* @param {number} px - 设计稿像素值
* @returns {number} 实际像素值
*/
getRealPx(px) {
const vw = this.viewportManager.getViewportWidth() / 100;
return (px / this.designWidth) * 100 * vw;
}
}
- 创建 LESS 混入
// 基准设计稿宽度
@design-width: 750;
// px 转 vw 混入
.px2vw(@property, @px) {
@{property}: calc(@px / @design-width * 100 * var(--vw));
}
// 尺寸设置混入
.size(@width, @height) {
.px2vw(width, @width);
.px2vw(height, @height);
}
// 字体大小混入
.font-size(@px) {
.px2vw(font-size, @px);
}
- 创建适配组件
import {ViewportManager} from '../utils/viewport';
import {MobileAdapter} from '../utils/adapter';
export class ViewportAdapter {
constructor() {
this.viewportManager = new ViewportManager();
this.mobileAdapter = new MobileAdapter();
this.init();
}
init() {
// 设置 viewport meta
this.setViewportMeta();
// 初始化事件监听
this.initEventListeners();
}
setViewportMeta() {
const meta = document.createElement('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content',
'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
);
document.head.appendChild(meta);
}
initEventListeners() {
// 监听屏幕旋转
window.addEventListener('orientationchange', () => {
setTimeout(() => {
this.viewportManager.setViewportWidth();
}, 300);
});
// 监听页面加载完成
window.addEventListener('load', () => {
this.viewportManager.setViewportWidth();
});
}
}
- 使用示例
import {ViewportAdapter} from '../components/ViewportAdapter';
import {MobileAdapter} from '../utils/adapter';
class Demo {
constructor() {
// 初始化视口适配
this.viewportAdapter = new ViewportAdapter();
this.mobileAdapter = new MobileAdapter();
this.init();
}
init() {
// 示例:动态设置元素宽度
const element = document.querySelector('.demo-element');
const width = this.mobileAdapter.getRealPx(200);
element.style.width = `${width}px`;
}
}
- 样式应用示例
@import './mixins.less';
.demo-container {
.size(750, 1334);
background: #fff;
.header {
.size(750, 88);
.px2vw(padding-left, 32);
.px2vw(padding-right, 32);
&-title {
.font-size(32);
color: #333;
}
}
.content {
.px2vw(margin-top, 20);
.px2vw(padding, 32);
&-item {
.size(686, 200);
.px2vw(margin-bottom, 20);
.px2vw(border-radius, 16);
background: #f5f5f5;
&-title {
.font-size(28);
.px2vw(line-height, 40);
color: #666;
}
}
}
}
- HTML 结构示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>移动端适配示例</title>
</head>
<body>
<div class="demo-container">
<header class="header">
<h1 class="header-title">移动端适配示例</h1>
</header>
<main class="content">
<div class="content-item">
<h2 class="content-item-title">内容区域</h2>
</div>
</main>
</div>
<script>
// 初始化适配
new Demo();
</script>
</body>
</html>
核心解决思路
- 使用 CSS 变量存储真实视口宽度单位
- 通过 JavaScript 动态计算和更新视口宽度
- 使用 LESS 混入统一管理单位转换
- 提供完整的适配工具类和组件
- 监听相关事件确保适配的实时性
优点
- 精确控制视口宽度
- 适应不同 App 内 WebView 的特殊处理
- 统一的开发体验
- 良好的可维护性
- 支持动态适配
注意事项
- 需要在页面初始化时就执行适配逻辑
- 注意性能优化,避免频繁计算
- 考虑兼容性问题
- 注意屏幕旋转的处理
这套解决方案可以有效解决移动端 WebView 中 100vw 与 window.innerWidth 不一致的问题,同时提供了完整的适配方案。