bry-auto-adapt
Version:
智能屏幕适配插件,自动将CSS中的px单位转换为vw/vh
251 lines (222 loc) • 8.77 kB
JavaScript
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;