postcss-mobile-forever
Version:
PostCSS 伸缩视图转换插件。To adapt different displays by one mobile viewport.
396 lines (376 loc) • 15 kB
JavaScript
const { marginL, marginR, maxWidth, shadowBorder, minFullHeight, autoHeight, lengthProps, fixedPos, autoDir, sideL, sideR, top, bottom, width, minWidth, minDFullHeight, dFullHeight, layoutContain, fullHVh } = require("./constants");
const { bookObj: b } = require("./utils");
const { convertPropValue, } = require("./logic-helper");
const { varTestReg } = require("./regexs");
const fs = require("fs");
const path = require("path");
function appendDemoContent(postcss, selector, rule, desktopViewAtRule, landScapeViewAtRule, disableDesktop, disableLandscape, disableMobile) {
if (!disableMobile) {
rule.append({
prop: "content",
value: "'✨Portrait✨'",
});
}
if (!disableDesktop) {
desktopViewAtRule.append(postcss.rule({ selector }).append({
prop: "content",
value: "'✨Desktop✨'",
}));
}
if (!disableLandscape) {
landScapeViewAtRule.append(postcss.rule({ selector }).append({
prop: "content",
value: "'✨Landscape✨'",
}));
}
}
function extractFile(cssContent, newFile, targetFileDir) {
return new Promise(resolve => {
const newFilePath = path.join(targetFileDir, newFile);
if (!fs.existsSync(targetFileDir)) {
fs.mkdirSync(targetFileDir, { recursive: true });
}
fs.writeFileSync(newFilePath, cssContent);
resolve();
});
}
/** 桌面和横屏添加选择器属性 */
function appendDisplaysRule(enabledDesktop, enabledLandscape, prop, val, important, selector, postcss, {
sharedAtRule,
desktopViewAtRule,
landScapeViewAtRule,
isShare,
}) {
if (enabledDesktop && enabledLandscape && isShare) {
sharedAtRule.append(postcss.rule({ selector }).append({
prop: prop, // 属性
value: val,
important, // 值的尾部有 important 则添加
}));
} else {
if (enabledDesktop) {
desktopViewAtRule.append(postcss.rule({ selector }).append({
prop: prop, // 属性
value: val,
important, // 值的尾部有 important 则添加
}));
}
if (enabledLandscape) {
landScapeViewAtRule.append(postcss.rule({ selector }).append({
prop: prop, // 属性
value: val,
important, // 值的尾部有 important 则添加
}));
}
}
}
/** px 值,转换为媒体查询中比例计算的 px,替换为移动端竖屏视口单位 */
function appendMediaRadioPxOrReplaceMobileVwFromPx(postcss, selector, prop, val, disableDesktop, disableLandscape, disableMobile, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
important,
decl,
convertLandscape,
convertDesktop,
convertMobile,
matchPercentage,
expectedLengthVars = [],
disableAutoApply = false,
isLastProp,
isKeyframesAtRule,
desktopKeyframesAtRule,
landscapeKeyframesAtRule,
}) {
decl.book = true;
const enabledDesktop = !disableDesktop;
const enabledLandscape = !disableLandscape;
const enabledMobile = !disableMobile;
if (enabledDesktop || enabledLandscape || enabledMobile) {
const { mobile, desktop, landscape, book: converted } = convertPropValue(prop, val, {
enabledMobile,
enabledDesktop,
enabledLandscape,
convertMobile,
convertDesktop,
convertLandscape,
matchPercentage,
});
if (enabledMobile) {
decl.value = mobile;
}
if (enabledDesktop && converted) {
const atRule = isKeyframesAtRule ? desktopKeyframesAtRule : desktopViewAtRule;
atRule.append(postcss.rule({ selector }).append({
prop: prop, // 属性
value: desktop, // 替换 px 比例计算后的值
important, // 值的尾部有 important 则添加
}));
}
if (enabledLandscape && converted) {
const atRule = isKeyframesAtRule ? landscapeKeyframesAtRule : landScapeViewAtRule;
atRule.append(postcss.rule({ selector }).append({
prop,
value: landscape,
important,
}));
}
let shouldAppendDesktopVar = false;
let shouldAppendLandscape = false;
if (
(enabledDesktop || enabledLandscape) &&
// 值没有被转换
!converted) {
if (
(expectedLengthVars.length > 0 && expectedLengthVars.some(varStr => val.includes(varStr))) ||
(expectedLengthVars.length === 0 && !disableAutoApply && lengthProps.includes(prop) && varTestReg.test(val))) {
shouldAppendDesktopVar = enabledDesktop;
shouldAppendLandscape = enabledLandscape;
}
}
appendCSSVar(shouldAppendDesktopVar, shouldAppendLandscape, prop, val, important, selector, postcss, {
sharedAtRule,
desktopViewAtRule,
landScapeViewAtRule,
isLastProp,
});
}
}
/** 由于不能直接检测 css 变量的值,不能获取变量内部是否包含 px 等单位,所以无法转换,因此将 css 变量统一移到共享媒体查询中,防止 css 变量被转换过的低优先级的属性覆盖 */
function appendCSSVar(enabledDesktop, enabledLandscape, prop, val, important, selector, postcss, {
sharedAtRule,
desktopViewAtRule,
landScapeViewAtRule,
isLastProp,
}) {
appendDisplaysRule(enabledDesktop, enabledLandscape, prop, val, important, selector, postcss, {
sharedAtRule,
desktopViewAtRule,
landScapeViewAtRule,
isShare: isLastProp,
});
}
/** 居中最外层选择器,margin 居中,无 border */
function appendMarginCentreRootClassNoBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth
}) {
if (disableDesktop && !disableLandscape) {
// 仅移动端横屏
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth), marginL, marginR));
} else if (disableLandscape && !disableDesktop) {
// 仅桌面
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth), marginL, marginR));
} else if (!disableDesktop && !disableLandscape) {
// 桌面和移动端横屏
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth)));
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth)));
sharedAtRule.append(postcss.rule({ selector }).prepend(marginL, marginR));
}
}
function appendMarginCentreBodyClassNoBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth
}) {
if (disableDesktop && !disableLandscape) {
// 仅移动端横屏
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth), marginL, marginR, fullHVh, layoutContain));
} else if (disableLandscape && !disableDesktop) {
// 仅桌面
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth), marginL, marginR, fullHVh, layoutContain));
} else if (!disableDesktop && !disableLandscape) {
// 桌面和移动端横屏
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth)));
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth)));
sharedAtRule.append(postcss.rule({ selector }).prepend(marginL, marginR, fullHVh, layoutContain));
}
}
/** 居中最外层选择器,用 margin 居中,有 border */
function appendMarginCentreRootClassWithBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
dvhAtRule,
desktopWidth,
landscapeWidth,
borderColor,
}) {
if (!disableDesktop || !disableLandscape) {
if (dvhAtRule.nodes.length === 0)
dvhAtRule.append(postcss.rule({ selector }).append(minDFullHeight));
}
if (disableDesktop && !disableLandscape) {
// 仅移动端横屏
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth), marginL, marginR, shadowBorder(borderColor), minFullHeight, autoHeight));
} else if (disableLandscape && !disableDesktop) {
// 仅桌面
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth), marginL, marginR, shadowBorder(borderColor), minFullHeight, autoHeight));
} else if (!disableDesktop && !disableLandscape) {
// 桌面和移动端横屏
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth)));
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth)));
sharedAtRule.append(postcss.rule({ selector }).prepend(marginL, marginR, shadowBorder(borderColor), minFullHeight, autoHeight));
}
}
function appendMarginCentreBodyClassWithBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth,
borderColor,
}) {
if (disableDesktop && !disableLandscape) {
// 仅移动端横屏
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth), fullHVh, marginL, marginR, shadowBorder(borderColor), layoutContain));
} else if (disableLandscape && !disableDesktop) {
// 仅桌面
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth), fullHVh, marginL, marginR, shadowBorder(borderColor), layoutContain));
} else if (!disableDesktop && !disableLandscape) {
// 桌面和移动端横屏
desktopViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(desktopWidth)));
landScapeViewAtRule.append(postcss.rule({ selector }).prepend(maxWidth(landscapeWidth)));
sharedAtRule.append(postcss.rule({ selector }).prepend(marginL, marginR, shadowBorder(borderColor), layoutContain, fullHVh));
}
}
function appendRemFontSize(rule, basicViewWidth) {
rule.prepend(b({
prop: 'font-size',
value: `${10000 / basicViewWidth}vw`, // 100 / basicViewWidth * 100
important: true,
}))
}
function appendCentreRoot(postcss, selector, disableDesktop, disableLandscape, border, {
rule,
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
dvhAtRule,
desktopWidth,
landscapeWidth,
maxWidthMode,
maxDisplayWidth,
minDisplayWidth,
}) {
const hadBorder = !!border;
const c = typeof border === "string" ? border : "#8888881f";
const isClamp = minDisplayWidth != null;
if (maxWidthMode) {
if (hadBorder) {
rule.prepend(b(maxWidth(maxDisplayWidth)), b(marginL), b(marginR), b(shadowBorder(c)), b(minFullHeight), b(autoHeight));
if (dvhAtRule.nodes.length === 0) dvhAtRule.append(postcss.rule({ selector }).append(b(minDFullHeight)));
}
else {
rule.prepend(b(maxWidth(maxDisplayWidth)), b(marginL), b(marginR));
isClamp && rule.prepend(b(minWidth(minDisplayWidth)));
}
rule.processedLimitedCentreWidth = true; // 做标记,防止死循环
} else {
if (hadBorder) {
appendMarginCentreRootClassWithBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
dvhAtRule,
desktopWidth,
landscapeWidth,
borderColor: c,
});
} else {
appendMarginCentreRootClassNoBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth,
});
}
}
}
function appendCentreBody(postcss, selector, disableDesktop, disableLandscape, border, {
rule,
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth,
dvhAtRule,
maxWidthMode,
maxDisplayWidth,
minDisplayWidth,
}) {
const hadBorder = !!border;
const c = typeof border === "string" ? border : "#8888881f";
const isClamp = minDisplayWidth != null;
if (maxWidthMode) {
if (hadBorder) {
rule.prepend(b(maxWidth(maxDisplayWidth)), b(marginL), b(marginR), b(shadowBorder(c)), b(fullHVh), b(layoutContain));
}
else {
rule.prepend(b(maxWidth(maxDisplayWidth)), b(marginL), b(marginR), b(layoutContain), b(fullHVh));
isClamp && rule.prepend(b(minWidth(minDisplayWidth)));
}
if (dvhAtRule.nodes.length === 0) dvhAtRule.append(postcss.rule({ selector }).append(b(dFullHeight)));
rule.processedLimitedCentreWidth = true; // 做标记,防止死循环
} else {
if (hadBorder) {
appendMarginCentreBodyClassWithBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth,
borderColor: c,
});
} else {
appendMarginCentreBodyClassNoBorder(postcss, selector, disableDesktop, disableLandscape, {
desktopViewAtRule,
landScapeViewAtRule,
sharedAtRule,
desktopWidth,
landscapeWidth,
});
}
if (!disableDesktop || !disableLandscape) {
if (dvhAtRule.nodes.length === 0)
dvhAtRule.append(postcss.rule({ selector }).append(dFullHeight));
}
}
}
function appendSiders(postcss, siders, desktopWidth, maxLandscapeDisplayHeight) {
const defaultSideW = 190;
const side1W = siders[0].width ?? defaultSideW;
const side2W = siders[1].width ?? defaultSideW;
const side3W = siders[2].width ?? defaultSideW;
const side4W = siders[3].width ?? defaultSideW;
const sideRule1 = siders[0].selector != null ? postcss.rule({ selector: siders[0].selector }).append(fixedPos, top(siders[0].gap), sideL(desktopWidth, siders[0].gap, side1W), autoDir("right"), autoDir("bottom"), width(side1W)) : null;
const sideRule2 = siders[1].selector != null ? postcss.rule({ selector: siders[1].selector }).append(fixedPos, top(siders[1].gap), autoDir("left"), sideR(desktopWidth, siders[1].gap, side2W), autoDir("bottom"), width(side2W)) : null;
const sideRule3 = siders[2].selector != null ? postcss.rule({ selector: siders[2].selector }).append(fixedPos, autoDir("top"), autoDir("left"), sideR(desktopWidth, siders[2].gap, side3W), bottom(siders[2].gap), width(side3W)) : null;
const sideRule4 = siders[3].selector != null ? postcss.rule({ selector: siders[3].selector }).append(fixedPos, autoDir("top"), sideL(desktopWidth, siders[3].gap, side4W), autoDir("right"), bottom(siders[3].gap), width(side4W)) : null;
const sidersRule = [[siders[0], sideRule1], [siders[1], sideRule2], [siders[2], sideRule3], [siders[3], sideRule4]].filter(s => s[1] != null);
const atRules = sidersRule.reduce((acc, [side, rule]) => {
const sideW = side.width ?? defaultSideW
const atRule = postcss
.atRule({ name: "media", params: `(min-width: ${desktopWidth + sideW * 2 + side.gap * 2}px) and (min-height: ${maxLandscapeDisplayHeight}px)`, nodes: [] })
.append(rule);
return acc.concat(atRule);
}, []);
return atRules;
}
module.exports = {
appendMediaRadioPxOrReplaceMobileVwFromPx,
appendDemoContent,
appendCentreRoot,
appendCentreBody,
appendCSSVar,
appendSiders,
appendDisplaysRule,
extractFile,
appendRemFontSize,
};