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