370 lines
10 KiB
JavaScript
370 lines
10 KiB
JavaScript
|
|
//屏幕适配工具类 -解决小程序滚动条问题
|
|||
|
|
|
|||
|
|
class ScreenAdapter {
|
|||
|
|
constructor() {
|
|||
|
|
this.systemInfo = null; // 存储设备系统信息(如屏幕尺寸,像素比等)
|
|||
|
|
this.isInitialized = false; // 标记适配器是否已经初始化
|
|||
|
|
this.deviceInfo = {}; // 存储设备详细信息(如信号,系统版本等)
|
|||
|
|
this.adaptationInfo = {} // 存储计算后的适配参数(如滚动区域高度,导航栏高度)
|
|||
|
|
}
|
|||
|
|
// 初始化适配器
|
|||
|
|
async init() {
|
|||
|
|
if (this.isInitialized) {
|
|||
|
|
return this.adaptationInfo;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 使用新的API 获取系统信息
|
|||
|
|
const [windowInfo, deviceInfo, appBaseInfo] = await Promise.all([
|
|||
|
|
this.getWindowInfo(),
|
|||
|
|
this.getDeviceInfo(),
|
|||
|
|
this.getAppBaseInfo()
|
|||
|
|
]);
|
|||
|
|
// 合并系统信息
|
|||
|
|
this.systemInfo = {
|
|||
|
|
...windowInfo,
|
|||
|
|
...deviceInfo,
|
|||
|
|
...appBaseInfo
|
|||
|
|
}
|
|||
|
|
//计算适配信息
|
|||
|
|
this.calculateAdaptation();
|
|||
|
|
this.isInitialized = true;
|
|||
|
|
return this.adaptationInfo;
|
|||
|
|
} catch (error) {
|
|||
|
|
|
|||
|
|
this.fallbackInit();
|
|||
|
|
return this.adaptationInfo;
|
|||
|
|
|
|||
|
|
}finally{
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
// 获取窗口信息(新API)
|
|||
|
|
getWindowInfo() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
try {
|
|||
|
|
|
|||
|
|
const windowInfo = wx.getWindowInfo();
|
|||
|
|
|
|||
|
|
resolve(windowInfo)
|
|||
|
|
} catch (error) {
|
|||
|
|
// 同步失败用异步
|
|||
|
|
wx.getWindowInfo({
|
|||
|
|
success: (res) => resolve(res),
|
|||
|
|
fail: () => {
|
|||
|
|
// 失败返回默认值
|
|||
|
|
resolve({
|
|||
|
|
windowWidth: 375,
|
|||
|
|
windowHeight: 667,
|
|||
|
|
pixelRatio: 2,
|
|||
|
|
statusBarHeight: 20,
|
|||
|
|
platform: 'android',
|
|||
|
|
system: 'Android 16',
|
|||
|
|
model: 'Unknown Device',
|
|||
|
|
brand: 'Unknown',
|
|||
|
|
safeArea: {
|
|||
|
|
top: 20,
|
|||
|
|
bottom: 667,
|
|||
|
|
left: 0,
|
|||
|
|
right: 375
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// 获取设备信息
|
|||
|
|
getDeviceInfo() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
try {
|
|||
|
|
const deviceInfo = wx.getDeviceInfo()
|
|||
|
|
resolve(deviceInfo)
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
resolve(
|
|||
|
|
{
|
|||
|
|
platform: 'unknown',
|
|||
|
|
system: 'unknown',
|
|||
|
|
model: 'unknown'
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// 获取应用基础信息
|
|||
|
|
getAppBaseInfo() {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
try {
|
|||
|
|
const appBaseInfo = wx.getAppBaseInfo()
|
|||
|
|
resolve(appBaseInfo)
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
resolve({
|
|||
|
|
version: '1.0.0',
|
|||
|
|
language: 'zh_CN'
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
// 兜底初始化
|
|||
|
|
fallbackInit() {
|
|||
|
|
try {
|
|||
|
|
// this.systemInfo = wx.getWindowInfo()
|
|||
|
|
this.systemInfo = this.getWindowInfo()
|
|||
|
|
|
|||
|
|
this.calculateAdaptation()
|
|||
|
|
this.isInitialized = true
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
this.useDefaultValues();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 计算适配信息
|
|||
|
|
calculateAdaptation() {
|
|||
|
|
const { windowWidth, screenHeight,windowHeight, screenTop,safeArea, model, pixelRatio = 2 } = this.systemInfo;
|
|||
|
|
|
|||
|
|
// 计算安全区域
|
|||
|
|
const statusBarHeight = safeArea ? safeArea.top : 44;
|
|||
|
|
const safeAreaBottom = safeArea ? ((windowHeight > safeArea.bottom) ? windowHeight - safeArea.bottom : 0) : 0;
|
|||
|
|
// 计算胶囊按钮信息(兜底处理)
|
|||
|
|
let menuButtonInfo = { height: 32, top: 6, width: 87, right: 281 };
|
|||
|
|
try {
|
|||
|
|
menuButtonInfo = wx.getMenuButtonBoundingClientRect();
|
|||
|
|
} catch (error) {
|
|||
|
|
console.warn('获取胶囊按钮信息失败,使用默认值 :', error);
|
|||
|
|
}
|
|||
|
|
const menuButtonHeight = menuButtonInfo.height;
|
|||
|
|
const menuButtonTop = menuButtonInfo.top - statusBarHeight;
|
|||
|
|
const targetModels = ['windows', 'huawei', 'nexus', 'mac'];
|
|||
|
|
const lowerModel = model.toLocaleLowerCase();
|
|||
|
|
const isTargetModel = targetModels.some(keyword => lowerModel.includes(keyword));
|
|||
|
|
const navBarHeight = isTargetModel ? (menuButtonInfo.bottom + menuButtonTop):(menuButtonInfo.bottom + menuButtonTop*2);
|
|||
|
|
// 计算可用高度(解决滚动条问题的关键)
|
|||
|
|
const usableHeight = windowHeight;
|
|||
|
|
const contentHeight = usableHeight - statusBarHeight - safeAreaBottom;
|
|||
|
|
// 设备类型判断
|
|||
|
|
const deviceType = this.detectDeviceType(windowWidth, windowHeight);
|
|||
|
|
|
|||
|
|
this.adaptationInfo = {
|
|||
|
|
// 基础信息
|
|||
|
|
windowWidth,
|
|||
|
|
windowHeight: deviceType.toLocaleLowerCase().includes('iPad') ? windowHeight : contentHeight,
|
|||
|
|
pixelRatio,
|
|||
|
|
screenHeight,
|
|||
|
|
|
|||
|
|
// 安全区域
|
|||
|
|
statusBarHeight,
|
|||
|
|
safeAreaBottom,
|
|||
|
|
safeAreaTop: statusBarHeight,
|
|||
|
|
// safeAreaLeft: safeArea ? safeArea.left : 0,
|
|||
|
|
// safeAreaRight: safeArea ? (windowWidth - safeArea.right) : 0,
|
|||
|
|
|
|||
|
|
// safeTop: model === 'Windows' ? navBarHeight : 0,
|
|||
|
|
screenTop,
|
|||
|
|
// centerOffset: windowWidth <= 430 ? 0 : (windowWidth - 430) / 2,
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 导航栏信息
|
|||
|
|
navBarHeight,
|
|||
|
|
menuButtonHeight,
|
|||
|
|
menuButtonTop,
|
|||
|
|
menuButtonInfo,
|
|||
|
|
|
|||
|
|
// 可用区域(解决滚动条的关键)
|
|||
|
|
usableHeight,
|
|||
|
|
contentHeight,
|
|||
|
|
|
|||
|
|
// 设备信息
|
|||
|
|
deviceType,
|
|||
|
|
platform: this.systemInfo.platform,
|
|||
|
|
|
|||
|
|
// CSS变量(用于动态设置样式)
|
|||
|
|
cssVars: {
|
|||
|
|
'--window-height': windowHeight + 'px',
|
|||
|
|
'--window-width': windowWidth + 'px',
|
|||
|
|
'--status-bar-height': statusBarHeight + 'px',
|
|||
|
|
'--nav-bar-height': navBarHeight + 'px',
|
|||
|
|
'--safe-area-bottom': safeAreaBottom + 'px',
|
|||
|
|
'--content-height': contentHeight + 'px',
|
|||
|
|
'--usable-height': usableHeight + 'px'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检测设备类型
|
|||
|
|
detectDeviceType(width, height) {
|
|||
|
|
// iPhone SE系列
|
|||
|
|
if (width <= 375 && height <= 667) {
|
|||
|
|
return 'iphone-se';
|
|||
|
|
}
|
|||
|
|
// iPhone 12/13/14系列
|
|||
|
|
else if (width <= 390 && height >= 844) {
|
|||
|
|
return 'iphone-standard';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// iPhone 12/13/14/15 Pro Max系列
|
|||
|
|
else if (width <= 430 && height >= 926) {
|
|||
|
|
return 'iphone-max';
|
|||
|
|
}
|
|||
|
|
// iPad系列
|
|||
|
|
else if (width >= 768) {
|
|||
|
|
return 'ipad';
|
|||
|
|
}
|
|||
|
|
// Android大屏
|
|||
|
|
else if (height >= 800) {
|
|||
|
|
return 'android-large';
|
|||
|
|
}
|
|||
|
|
// 其他设备
|
|||
|
|
else {
|
|||
|
|
return 'unknown';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 使用默认值
|
|||
|
|
useDefaultValues() {
|
|||
|
|
this.adaptationInfo = {
|
|||
|
|
windowWidth: 375,
|
|||
|
|
windowHeight: 667,
|
|||
|
|
pixelRatio: 2,
|
|||
|
|
statusBarHeight: 44,
|
|||
|
|
safeAreaBottom: 0,
|
|||
|
|
safeAreaTop: 44,
|
|||
|
|
safeAreaLeft: 0,
|
|||
|
|
safeAreaRight: 0,
|
|||
|
|
navBarHeight: 88,
|
|||
|
|
menuButtonHeight: 32,
|
|||
|
|
menuButtonTop: 6,
|
|||
|
|
usableHeight: 667,
|
|||
|
|
contentHeight: 623,
|
|||
|
|
deviceType: 'unknown',
|
|||
|
|
platform: 'unknown',
|
|||
|
|
cssVars: {
|
|||
|
|
'--window-height': '667px',
|
|||
|
|
'--window-width': '375px',
|
|||
|
|
'--status-bar-height': '44px',
|
|||
|
|
'--nav-bar-height': '88px',
|
|||
|
|
'--safe-area-bottom': '0px',
|
|||
|
|
'--content-height': '623px',
|
|||
|
|
'--usable-height': '667px'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
this.isInitialized = true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取适配信息
|
|||
|
|
getAdaptationInfo() {
|
|||
|
|
if (!this.isInitialized) {
|
|||
|
|
console.warn('屏幕适配器未初始化,返回默认值');
|
|||
|
|
this.useDefaultValues();
|
|||
|
|
}
|
|||
|
|
return this.adaptationInfo;
|
|||
|
|
}
|
|||
|
|
// 为页面设置适配样式(解决滚动条的核心方法)
|
|||
|
|
applyToPage(pageInstance) {
|
|||
|
|
if (!pageInstance) {
|
|||
|
|
console.error('页面实例为空');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const info = this.getAdaptationInfo();
|
|||
|
|
console.log('应用适配信息到页面:', info);
|
|||
|
|
|
|||
|
|
// 🔥 设置页面数据 - 使用Object.assign替代扩展运算符
|
|||
|
|
pageInstance.setData(Object.assign({}, info, {
|
|||
|
|
// 兼容旧版本的字段名
|
|||
|
|
windowHeight: info.windowHeight,
|
|||
|
|
windowWidth: info.windowWidth,
|
|||
|
|
statusBarHeight: info.statusBarHeight,
|
|||
|
|
navBarHeight: info.navBarHeight,
|
|||
|
|
safeAreaBottom: info.safeAreaBottom,
|
|||
|
|
menuButtonHeight: info.menuButtonHeight,
|
|||
|
|
menuButtonTop: info.menuButtonTop
|
|||
|
|
}));
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('应用适配信息到页面失败:', error);
|
|||
|
|
// 不抛出错误,避免影响页面加载
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 应用CSS变量
|
|||
|
|
applyCSSVars() {
|
|||
|
|
if (!this.adaptationInfo.cssVars) return;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
// 在小程序中,我们通过设置页面数据来传递这些值
|
|||
|
|
// CSS变量将通过页面数据在WXML中使用
|
|||
|
|
const pages = getCurrentPages();
|
|||
|
|
const currentPage = pages[pages.length - 1];
|
|||
|
|
if (currentPage) {
|
|||
|
|
currentPage.setData({
|
|||
|
|
screenCSSVars: this.adaptationInfo.cssVars
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('应用CSS变量失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 监听窗口大小变化(横竖屏切换等)
|
|||
|
|
onWindowResize(callback) {
|
|||
|
|
wx.onWindowResize((res) => {
|
|||
|
|
|
|||
|
|
// 重新计算适配信息
|
|||
|
|
this.systemInfo.windowWidth = res.size.windowWidth;
|
|||
|
|
this.systemInfo.windowHeight = res.size.windowHeight;
|
|||
|
|
this.calculateAdaptation();
|
|||
|
|
|
|||
|
|
if (callback && typeof callback === 'function') {
|
|||
|
|
callback(this.adaptationInfo);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取推荐的页面配置
|
|||
|
|
getPageConfig() {
|
|||
|
|
return {
|
|||
|
|
// 禁用页面滚动,防止出现滚动条
|
|||
|
|
disableScroll: true,
|
|||
|
|
// 背景色
|
|||
|
|
backgroundColor: '#f8f9fa',
|
|||
|
|
// 导航栏样式
|
|||
|
|
navigationBarBackgroundColor: '#667eea',
|
|||
|
|
navigationBarTextStyle: 'white'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建全局实例
|
|||
|
|
const screenAdapter = new ScreenAdapter();
|
|||
|
|
|
|||
|
|
// 便捷方法
|
|||
|
|
async function init(self) {
|
|||
|
|
try {
|
|||
|
|
await screenAdapter.init();
|
|||
|
|
screenAdapter.applyToPage(self);
|
|||
|
|
// onWindowResize 是监听器,不需要 await
|
|||
|
|
screenAdapter.onWindowResize(() => {
|
|||
|
|
try {
|
|||
|
|
screenAdapter.applyToPage(self);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('窗口大小变化时应用适配信息失败:', error);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('初始化屏幕信息出错了', error);
|
|||
|
|
// 即使初始化失败,也尝试应用默认值
|
|||
|
|
try {
|
|||
|
|
screenAdapter.applyToPage(self);
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('应用默认适配信息失败:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 导出实例和类
|
|||
|
|
module.exports = {
|
|||
|
|
init
|
|||
|
|
};
|
|||
|
|
|