/** * 视口和缩放处理器 * 处理页面缩放、视口变化和响应式布局 */ 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; }