該方法會根據?目標分辨率(options.width/height)?和?當前窗口尺寸(innerWidth/innerHeight)?計算縮放比例,并保持?等比例縮放(Math.min(scaleX, scaleY)
),確保內容不變形:
/*** 防抖函數* @param {Function} fn - 需要防抖的函數* @param {number} delay - 延遲時間(ms)* @returns {Function} 防抖后的函數*/
function debounce(fn, delay) {let timer = null;return function (...args) {if (timer) clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};
}/*** 帶滾動條支持的自適應縮放* @param {string|HTMLElement} target - 目標元素* @param {Object} config - 配置項* @param {number} config.designWidth - 設計稿寬度(如1920)* @param {number} config.designHeight - 設計稿總高度(如1080)* @param {number} [config.topOffset=60] - 頂部固定高度(如導航欄)* @param {boolean} [config.scrollable=true] - 是否啟用垂直滾動*/
export function scrollableAutoScale(target, config) {const el = typeof target === 'string' ? document.querySelector(target) : target;if (!el) return () => { };const {designWidth = 1920,designHeight = 1080,topOffset1 = 0,topOffset2 = 0,scrollable = true} = config;// 1. 設備像素比檢測和優化const dpr = window.devicePixelRatio || 1;const isRetina = dpr >= 1.5;const scalePrecision = isRetina ? 1000 : 400; // 視網膜屏使用更高精度// 2. 高清渲染樣式優化Object.assign(el.style, {willChange: 'transform',backfaceVisibility: 'hidden',transformStyle: 'preserve-3d',textRendering: isRetina ? 'geometricPrecision' : 'optimizeLegibility',WebkitFontSmoothing: isRetina ? 'subpixel-antialiased' : 'antialiased',imageRendering: isRetina ? 'crisp-edges' : 'auto'});// 3. 創建外層容器(添加過渡效果、增加視網膜屏優化)const container = document.createElement('div');Object.assign(container.style, {position: 'fixed',top: `${topOffset2}px`,left: '0',width: '100vw',height: `calc(100vh - ${topOffset1 + topOffset2}px)`,overflow: 'hidden', // 默認隱藏,后面動態調整margin: '0',padding: '0',zIndex: '1',// 背景圖優化backgroundImage: getComputedStyle(el).backgroundImage,backgroundPosition: getComputedStyle(el).backgroundPosition,backgroundRepeat: getComputedStyle(el).backgroundRepeat,backgroundSize: getComputedStyle(el).backgroundSize,imageRendering: isRetina ? 'crisp-edges' : 'auto',// 平滑過渡 滾動條出現/消失transition: 'overflow 0.2s ease-out'});el.style.backgroundImage = 'none';el.parentNode.insertBefore(container, el);container.appendChild(el);// 4. 目標元素樣式(使用3D變換)Object.assign(el.style, {position: 'absolute',transformOrigin: 'top left',width: `${designWidth}px`,left: '0',right: '0',transformStyle: 'preserve-3d'});// 5. 增強版縮放計算let lastScale = 0;const calculate = () => {const viewportWidth = container.clientWidth;const viewportHeight = container.clientHeight;// 高精度縮放計算const rawScale = viewportWidth / designWidth;const scale = Math.round(rawScale * scalePrecision) / scalePrecision;const contentHeight = designHeight * scale;// 動態閾值(視網膜屏使用更敏感的閾值)const scaleThreshold = isRetina ? 0.001 : 0.0025;if (Math.abs(scale - lastScale) > scaleThreshold) {// 使用scale3d提升渲染質量el.style.transform = `scale3d(${scale}, ${scale}, 1)`;// 垂直居中優化if (!scrollable) {el.style.transform = `scale3d(${scale}, ${scale}, 1) translate3d(0, ${(viewportHeight - contentHeight) / 2}px, 0)`;}lastScale = scale;}// 滾動條緩沖(視網膜屏增加緩沖)const scrollBuffer = isRetina ? 5 : 2;container.style.overflowY = scrollable && (contentHeight > viewportHeight + scrollBuffer)? 'auto': 'hidden';};// 6. 高性能監聽(雙RAF防抖)使用requestAnimationFrame優化監聽頻率,避免高頻觸發計算導致性能問題。let rafId = null;const optimizedCalculate = () => {if (rafId) cancelAnimationFrame(rafId);rafId = requestAnimationFrame(() => {calculate();rafId = requestAnimationFrame(() => {calculate(); // 雙RAF確保布局穩定rafId = null;});});};const observer = new ResizeObserver(debounce(optimizedCalculate, isRetina ? 30 : 50));observer.observe(container);// 7. 三重初始化保障requestAnimationFrame(() => {calculate();setTimeout(() => {requestAnimationFrame(calculate);}, 200);});return () => {observer.disconnect();if (rafId) cancelAnimationFrame(rafId);container.parentNode?.insertBefore(el, container);container.parentNode?.removeChild(container);};
}
react頁面中的調用:
import React, { useEffect, useRef } from 'react';
import { optimizedAutoScale } from '../utils/optimizedAutoScale';const Index = () => {const containerRef = useRef(null);useEffect(() => {if (!containerRef.current) return;const destroy = scrollableAutoScale(containerRef.current, {designWidth: 1920,minHeight: 1080, // 設計稿總高度topOffset1: 53, // 動態獲取頂部高度topOffset2: 42, // 動態獲取頂部高度scrollable: true, // 允許滾動});return () => {destroy();};}, []);return (<div ref={containerRef}>{/* 動態內容區域,高度會自動計算 */}</div>);
};export default Index;