first commit
This commit is contained in:
373
AIEC-server/js/viewport-handler.js
Normal file
373
AIEC-server/js/viewport-handler.js
Normal file
@ -0,0 +1,373 @@
|
||||
/**
|
||||
* 视口和缩放处理器
|
||||
* 处理页面缩放、视口变化和响应式布局
|
||||
*/
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user