UNPKG

bry-auto-adapt

Version:

智能屏幕适配插件,自动将CSS中的px单位转换为vw/vh

251 lines (222 loc) 8.77 kB
import postcss from 'postcss'; // 生成运行时脚本 const generateScript = (designWidth, designHeight, defaultMode = 'center') => { return ` <script> (function() { const designWidth = ${designWidth}; const designHeight = ${designHeight}; const designAspectRatio = designWidth / designHeight; let currentMode = '${defaultMode}'; // 默认居中模式 console.log('currentMode', currentMode); // 存储原始缩放比例 let originalWidthScale = 1; let originalHeightScale = 1; function updateScale() { if (currentMode === 'tile') { // 平铺模式:不进行缩放 document.documentElement.style.setProperty('--bry-width-scale', '1'); document.documentElement.style.setProperty('--bry-height-scale', '1'); return; } // 居中等比模式逻辑 const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const viewportAspectRatio = viewportWidth / viewportHeight; let scaleRatio; if (viewportAspectRatio > designAspectRatio) { scaleRatio = (viewportHeight / designHeight) / (viewportWidth / designWidth); originalWidthScale = scaleRatio; originalHeightScale = 1; } else { scaleRatio = (viewportWidth / designWidth) / (viewportHeight / designHeight); originalWidthScale = 1; originalHeightScale = scaleRatio; } // 应用缩放比例 document.documentElement.style.setProperty('--bry-width-scale', originalWidthScale.toFixed(6)); document.documentElement.style.setProperty('--bry-height-scale', originalHeightScale.toFixed(6)); } // 初始化 updateScale(); window.addEventListener('resize', updateScale); window.addEventListener('orientationchange', updateScale); // 暴露切换适配模式的方法 window.bryScreenAdapter = { setMode: function(mode) { if (mode === 'center' || mode === 'tile') { currentMode = mode; updateScale(); return true; } return false; }, getMode: function() { return currentMode; }, getScale: function() { return { width: parseFloat(document.documentElement.style.getPropertyValue('--bry-width-scale') || 1), height: parseFloat(document.documentElement.style.getPropertyValue('--bry-height-scale') || 1) }; } }; })(); </script> `; }; // PostCSS 插件部分(独立函数) function createPostCSSPlugin(options = {}) { const { designWidth = 1920, designHeight = 1080, unit = 'vw', precision = 6, exclude = null, include = null, selectorBlackList = [], propertiesBlackList = [], mediaQuery = false, minPixelValue = 0, useRuntimeScaling = true, htmlBackgroundColor = '#0078fd', autoSetBodySize = true, enableHtmlFlex = true } = options; const designAspectRatio = designWidth / designHeight; return { postcssPlugin: 'bry-auto-adapt', Once(root, result) { // 检查是否排除当前文件 if (exclude && root.source && root.source.input.file) { const file = root.source.input.file; if (typeof exclude === 'string' && file.includes(exclude)) return; if (Array.isArray(exclude) && exclude.some(ex => file.includes(ex))) return; if (exclude instanceof RegExp && exclude.test(file)) return; } // 检查是否包含当前文件 if (include && root.source && root.source.input.file) { const file = root.source.input.file; if (typeof include === 'string' && !file.includes(include)) return; if (Array.isArray(include) && !include.some(inc => file.includes(inc))) return; if (include instanceof RegExp && !include.test(file)) return; } // 自动配置HTML和Body if (autoSetBodySize || htmlBackgroundColor || enableHtmlFlex) { const htmlRule = postcss.rule({ selector: 'html' }); const bodyRule = postcss.rule({ selector: 'body' }); // 设置HTML背景色 if (htmlBackgroundColor) { htmlRule.append({ prop: 'background-color', value: htmlBackgroundColor }); } // 启用HTML Flex布局 if (enableHtmlFlex) { htmlRule.append({ prop: 'display', value: 'flex' }); htmlRule.append({ prop: 'justify-content', value: 'center' }); htmlRule.append({ prop: 'align-items', value: 'center' }); htmlRule.append({ prop: 'min-height', value: '100vh' }); } // 设置Body尺寸 if (autoSetBodySize) { bodyRule.append({ prop: 'width', value: `${designWidth}px` }); bodyRule.append({ prop: 'height', value: `${designHeight}px` }); bodyRule.append({ prop: 'margin', value: '0' }); bodyRule.append({ prop: 'box-sizing', value: 'border-box' }); } root.prepend(htmlRule); root.prepend(bodyRule); } // 遍历所有CSS规则 root.walkRules(rule => { if (selectorBlackList.length > 0 && selectorBlackList.some(black => rule.selector.includes(black))) { return; } rule.walkDecls(decl => { if (propertiesBlackList.includes(decl.prop)) return; const value = decl.value; if (!/\d+(\.\d+)?px/.test(value)) return; decl.value = value.replace(/(\d+(\.\d+)?)px/g, (match, num, index) => { if (parseFloat(num) < minPixelValue) return match; const prop = decl.prop; let baseSize, targetUnit, scaleVariable = ''; if (prop.includes('width') || prop.includes('left') || prop.includes('right') || prop === 'margin-left' || prop === 'margin-right' || prop === 'padding-left' || prop === 'padding-right') { baseSize = designWidth; targetUnit = 'vw'; scaleVariable = useRuntimeScaling ? '*var(--bry-width-scale)' : ''; } else if (prop.includes('height') || prop.includes('top') || prop.includes('bottom') || prop === 'margin-top' || prop === 'margin-bottom' || prop === 'padding-top' || prop === 'padding-bottom') { baseSize = designHeight; targetUnit = 'vh'; scaleVariable = useRuntimeScaling ? '*var(--bry-height-scale)' : ''; } else if (prop === 'margin' || prop === 'padding') { const values = value.split(/\s+/); const valueCount = values.length; if (valueCount === 1) { baseSize = designHeight; targetUnit = 'vh'; scaleVariable = useRuntimeScaling ? '*var(--bry-height-scale)' : ''; } else if (valueCount === 2) { baseSize = index === 0 ? designHeight : designWidth; targetUnit = index === 0 ? 'vh' : 'vw'; scaleVariable = useRuntimeScaling ? (index === 0 ? '*var(--bry-height-scale)' : '*var(--bry-width-scale)') : ''; } } else { baseSize = designWidth; targetUnit = 'vw'; scaleVariable = useRuntimeScaling ? '*var(--bry-width-scale)' : ''; } const convertedValue = (parseFloat(num) / baseSize * 100).toFixed(precision); return useRuntimeScaling ? `calc(${convertedValue}${targetUnit}${scaleVariable})` : `${convertedValue}${targetUnit}`; }); }); }); // 处理媒体查询 if (mediaQuery) { root.walkAtRules('media', atRule => { atRule.params = atRule.params.replace(/(\d+(\.\d+)?)px/g, (match, num) => { if (parseFloat(num) < minPixelValue) return match; const convertedValue = (parseFloat(num) / designWidth * 100).toFixed(precision); return `${convertedValue}vw`; }); }); } } }; } // Vite 插件部分 export default function bryScreenAdapter(options = {}) { const { designWidth = 1920, designHeight = 1080, defaultMode = 'center' } = options; return { name: 'bry-screen-adapter', transformIndexHtml(html) { const script = generateScript(designWidth, designHeight, defaultMode); return html.replace(/<\/body\s*>/i, `${script}</body>`); }, config() { return { css: { postcss: { plugins: [ createPostCSSPlugin(options) ] } } }; } }; } bryScreenAdapter.postcss = true;