Files
AIEC-new/AIEC-server/js/viewport-handler.js
2025-10-17 09:31:28 +08:00

373 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 视口和缩放处理器
* 处理页面缩放、视口变化和响应式布局
*/
class ViewportHandler {
constructor() {
this.currentZoom = 1;
this.breakpoints = {
xs: 320,
sm: 640,
md: 768,
lg: 1024,
xl: 1280,
'2xl': 1536
};
this.init();
}
init() {
// 检测初始缩放级别
this.detectZoomLevel();
// 设置事件监听器
this.setupEventListeners();
// 应用初始视口设置
this.applyViewportSettings();
// 处理初始布局
this.handleViewportChange();
}
/**
* 检测浏览器缩放级别
*/
detectZoomLevel() {
// 方法1使用 window.devicePixelRatio
const pixelRatio = window.devicePixelRatio || 1;
// 方法2使用 outerWidth 和 innerWidth 比较
const zoomLevel = Math.round((window.outerWidth / window.innerWidth) * 100) / 100;
// 方法3使用媒体查询检测
const mqString = `(resolution: ${window.devicePixelRatio}dppx)`;
const mq = window.matchMedia(mqString);
this.currentZoom = pixelRatio;
this.updateZoomClasses();
}
/**
* 设置事件监听器
*/
setupEventListeners() {
// 监听窗口大小变化
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
this.handleViewportChange();
this.detectZoomLevel();
}, 250);
});
// 监听缩放变化
window.addEventListener('wheel', (e) => {
if (e.ctrlKey || e.metaKey) {
setTimeout(() => {
this.detectZoomLevel();
}, 100);
}
});
// 监听方向变化(移动设备)
window.addEventListener('orientationchange', () => {
setTimeout(() => {
this.handleViewportChange();
}, 500);
});
// 监听媒体查询变化
this.setupMediaQueryListeners();
}
/**
* 设置媒体查询监听器
*/
setupMediaQueryListeners() {
// 监听不同断点
Object.entries(this.breakpoints).forEach(([name, width]) => {
const mq = window.matchMedia(`(min-width: ${width}px)`);
mq.addListener((e) => {
if (e.matches) {
this.onBreakpointChange(name, width);
}
});
});
// 监听高DPI屏幕
const highDpiQuery = window.matchMedia('(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)');
highDpiQuery.addListener((e) => {
if (e.matches) {
document.body.classList.add('high-dpi');
} else {
document.body.classList.remove('high-dpi');
}
});
// 监听触摸设备
const touchQuery = window.matchMedia('(hover: none) and (pointer: coarse)');
touchQuery.addListener((e) => {
if (e.matches) {
document.body.classList.add('touch-device');
} else {
document.body.classList.remove('touch-device');
}
});
}
/**
* 处理视口变化
*/
handleViewportChange() {
const viewport = {
width: window.innerWidth,
height: window.innerHeight,
orientation: window.innerWidth > window.innerHeight ? 'landscape' : 'portrait',
zoom: this.currentZoom
};
// 更新CSS变量
this.updateCSSVariables(viewport);
// 调整布局
this.adjustLayout(viewport);
// 优化字体大小
this.optimizeFontSize(viewport);
// 处理特殊组件
this.handleSpecialComponents(viewport);
// 触发自定义事件
window.dispatchEvent(new CustomEvent('viewportChanged', { detail: viewport }));
}
/**
* 更新CSS变量
*/
updateCSSVariables(viewport) {
const root = document.documentElement;
// 视口尺寸
root.style.setProperty('--vw', `${viewport.width * 0.01}px`);
root.style.setProperty('--vh', `${viewport.height * 0.01}px`);
root.style.setProperty('--vmin', `${Math.min(viewport.width, viewport.height) * 0.01}px`);
root.style.setProperty('--vmax', `${Math.max(viewport.width, viewport.height) * 0.01}px`);
// 缩放相关
root.style.setProperty('--zoom-level', this.currentZoom);
root.style.setProperty('--base-font-size', `${16 / this.currentZoom}px`);
// 响应式间距
const spacingUnit = Math.max(4, Math.min(8, viewport.width / 200));
root.style.setProperty('--spacing-unit', `${spacingUnit}px`);
}
/**
* 更新缩放相关的CSS类
*/
updateZoomClasses() {
const body = document.body;
// 移除旧的缩放类
body.classList.remove('zoom-50', 'zoom-75', 'zoom-90', 'zoom-100', 'zoom-110', 'zoom-125', 'zoom-150', 'zoom-200');
// 添加新的缩放类
if (this.currentZoom <= 0.5) {
body.classList.add('zoom-50');
} else if (this.currentZoom <= 0.75) {
body.classList.add('zoom-75');
} else if (this.currentZoom <= 0.9) {
body.classList.add('zoom-90');
} else if (this.currentZoom <= 1.1) {
body.classList.add('zoom-100');
} else if (this.currentZoom <= 1.25) {
body.classList.add('zoom-110');
} else if (this.currentZoom <= 1.5) {
body.classList.add('zoom-125');
} else if (this.currentZoom <= 2) {
body.classList.add('zoom-150');
} else {
body.classList.add('zoom-200');
}
}
/**
* 调整布局
*/
adjustLayout(viewport) {
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
// 小屏幕自动折叠侧边栏
if (viewport.width < 768 && sidebar && !sidebar.classList.contains('collapsed')) {
// 触发侧边栏折叠
const toggleBtn = document.getElementById('toggleSidebar');
if (toggleBtn) {
toggleBtn.click();
}
}
// 调整聊天消息容器高度
const chatMessages = document.getElementById('chatMessages');
if (chatMessages) {
const headerHeight = document.querySelector('header')?.offsetHeight || 0;
const inputHeight = document.getElementById('chatModeInput')?.offsetHeight || 0;
const availableHeight = viewport.height - headerHeight - inputHeight - 40; // 40px for padding
chatMessages.style.maxHeight = `${availableHeight}px`;
}
}
/**
* 优化字体大小
*/
optimizeFontSize(viewport) {
// 根据视口宽度计算基础字体大小
let baseFontSize = 16;
if (viewport.width < 360) {
baseFontSize = 14;
} else if (viewport.width < 768) {
baseFontSize = 15;
} else if (viewport.width > 1920) {
baseFontSize = 18;
}
// 应用缩放调整
baseFontSize = baseFontSize / this.currentZoom;
// 设置根字体大小
document.documentElement.style.fontSize = `${baseFontSize}px`;
}
/**
* 处理特殊组件
*/
handleSpecialComponents(viewport) {
// 3D新闻卡片调整
const ticker3D = document.querySelector('.ticker-3d-container');
const tickerSection = document.querySelector('.ticker-section');
if (ticker3D) {
// 根据缩放级别调整3D卡片
if (this.currentZoom > 1.5) {
// 高缩放时隐藏
if (tickerSection) tickerSection.style.display = 'none';
} else if (this.currentZoom > 1.25) {
// 中等缩放时缩小
ticker3D.style.transform = 'scale(0.7)';
ticker3D.style.maxHeight = '80px';
if (tickerSection) tickerSection.style.display = 'block';
} else if (viewport.width < 768) {
ticker3D.style.transform = 'scale(0.8)';
if (tickerSection) tickerSection.style.display = 'block';
} else if (viewport.width < 1024) {
ticker3D.style.transform = 'scale(0.9)';
if (tickerSection) tickerSection.style.display = 'block';
} else {
ticker3D.style.transform = 'scale(1)';
ticker3D.style.maxHeight = '';
if (tickerSection) tickerSection.style.display = 'block';
}
// 3D卡片现在固定在底部不需要检测重叠
}
// 文字轮播调整
const rotatingWord = document.querySelector('.rotating-word-3d');
if (rotatingWord) {
if (viewport.width < 480) {
rotatingWord.style.fontSize = '1rem';
} else if (viewport.width < 768) {
rotatingWord.style.fontSize = '1.25rem';
} else {
rotatingWord.style.fontSize = '';
}
}
}
/**
* 应用视口设置
*/
applyViewportSettings() {
// 设置视口meta标签
let viewportMeta = document.querySelector('meta[name="viewport"]');
if (!viewportMeta) {
viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport';
document.head.appendChild(viewportMeta);
}
// 根据设备类型设置不同的视口配置
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
viewportMeta.content = 'width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes';
} else {
viewportMeta.content = 'width=device-width, initial-scale=1.0';
}
}
/**
* 断点变化处理
*/
onBreakpointChange(name, width) {
console.log(`Breakpoint changed to: ${name} (${width}px)`);
// 触发自定义事件
window.dispatchEvent(new CustomEvent('breakpointChanged', {
detail: { name, width }
}));
}
/**
* 获取当前断点
*/
getCurrentBreakpoint() {
const width = window.innerWidth;
let current = 'xs';
for (const [name, breakpoint] of Object.entries(this.breakpoints)) {
if (width >= breakpoint) {
current = name;
}
}
return current;
}
/**
* 检查是否为移动设备
*/
isMobile() {
return window.innerWidth < this.breakpoints.md ||
('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0);
}
/**
* 检查是否为高DPI屏幕
*/
isHighDPI() {
return window.devicePixelRatio > 1 ||
(window.matchMedia && window.matchMedia('(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)').matches);
}
/**
* 强制重新计算布局
*/
forceRecalculate() {
this.detectZoomLevel();
this.handleViewportChange();
}
}
// 创建全局实例
window.viewportHandler = new ViewportHandler();
// 导出给其他模块使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = ViewportHandler;
}