css-shorthand
Flex-flow 属性对齐
输入一个关键字
输入两个关键字:
- 两个关键字指定了同一个属性时非法
输入三个关键字:非法,全都采用默认值
是否有必要完全对齐浏览器? 比如 flex-flow 为了对齐标准会执行很多额外的逻辑,如果开发者写 style 的话,这部分的性能在运行时是有损耗的。
那么为了让开发者体验更好,做的额外逻损失了运行时性能,为了不损失性能又得推荐开发者写 css 而少用行内样式,这又降低了开发者体验(style 动态控制的情况是无法避免的)。
这里该如何平衡呢?如果是开发者使用属性的多寡来平衡似乎会进入死胡同:用得多 -> 对齐浏览器(提升体验) -> 损失性能、最好是写 css -> 影响体验。
最理想的情况是,开发者永远需要是正确的,框架不给开发者兜底,通过最佳实践 + stylelint 约束开发者,提升性能
const FLEX_DIRECTION_KEYWORD_MAP = {'row': 1, 'row-reverse': 1, 'column': 1, 'column-reverse': 1};
const FLEX_WRAP_KEYWORD_MAP = {'wrap': 1, 'nowrap': 1, 'wrap-reverse': 1};
function getCSSPropertyFlexDirection(property, val) {
return FLEX_DIRECTION_KEYWORD_MAP.hasOwnProperty(val)
? {property: property, value: val} : null;
}
function getCSSPropertyFlexWrap(property, val) {
return FLEX_WRAP_KEYWORD_MAP.hasOwnProperty(val)
? {property: property, value: val} : null;
}
// style_propery_shorthand.cc
function flexFlowShortHandle() {
return [
{property: 'flexDirection', parser: getCSSPropertyFlexDirection},
{property: 'flexWrap', parser: getCSSPropertyFlexWrap}
];
}
// 这个是种通用逻辑,针对这类 shorthand property 都可以使用
function consumeShorthandGreedilyViaLonghands(
shorhand,
valueTokenList
) {
const longhands = [];
for (const valueToken of valueTokenList) {
let foundLonghand = false;
for (let i = 0; !foundLonghand && i < shorhand.length; i++) {
if (longhands[i]) {
continue;
}
longhands[i] = shorthand[i].parser(shorthand[i].property, valueToken);
// 找到一个之后就不会再继续了,因为一个关键字只能匹配一个属性,没啥问题
if (longhands[i]) {
foundLonghand = true;
}
}
// valueToken 其中一个错了的话,直接返回 null
// 同时 写多了,但是没有写错 也会返回 null
// 写了相同的,导致第二个没有匹配到,也会返回 null? 相当于是 写多了 没写错的情况
if (!foundLonghand) {
return null;
}
}
const res = [];
for (let i = 0; i < shorthand.length; i++) {
if (longhands[i]) {
res.push(longhands[i]);
}
// 写少了才会进入这里
else {
res.push({property: shorthand[i].property, value: 'initial'});
// 我们支持 initial 么?
}
}
}
// shorthands_custom.cc
rules.flexFlow = (value) => {
const valueTokenList = value.split(/\s+/);
const res = consumeShorthandGreedilyViaLonghands(flexFlowShortHandle(), valueTokenList);
// ...
}
Border 简写属性
if (props.hasOwnProperty('borderStyle') && props.borderStyle !== 'none') {
if (!props.hasOwnProperty('borderWidth')) {
// borderWidth 默认值为 medium ,等价于 3px
props.borderWidth = 3;
}
if (!props.hasOwnProperty('borderColor') && props.hasOwnProperty('color')) {
// borderColor 默认值为 currentColor,即是当前元素的 color 属性值
props.borderColor = props.color;
}
}
在这里处理没用,必须要在 styleAdapter 处理, 如果为 none 把 borderStyle 等 delete 掉
border 没有 none 这个关键字,h5 下写 none 触发简写属性短路
function handleBorder (val: string, platform, side: string = ''): KeyValue[] {
if (val === 'none') {
// 在这里处理没用,必须要在 styleAdapter 处理, 如果为 none 把 borderStyle 等 delete 掉
return null;
}
}
重置简写属性是一个麻烦的工作
必须要在后置处理,如果简写属性是 none 之类的,需要逐个删除
简写属性难题
如果先写了 border 再写了 longhand 那要如何覆盖呢?无法判断 shorthand 和 longhand 谁先谁后,只能是在 shorthand 重置为 none 时就直接做删除,做不了,样式解析时拿不到 props
所以要用同一的简写属性处理,如果触发了短路,返回 longhand 属性的默认值
默认值最好是放在端上,前端没有必要传来传去的,反正都是默认值
简写属性的贪心算法肯定是慢点的
// 为 number 保留 n 位小数,默认为 4
function roundFun (value, n = 4) {
return Math.round(value * Math.pow(10, n)) / Math.pow(10, n);
}
function handleNumber (val, fn = parseFloat) {
if (typeof val !== 'number') {
let number = fn(val);
if (isNaN(number)) {
return null;
}
// 保留小数点后 4 位
// 客户端接受数值精度 4 位即可,减少通信体积
number = roundFun(number);
// 支持vw/vh
if (val.endsWith('vh')) {
return number + 'vh';
}
if (val.endsWith('vw')) {
return number + 'vw';
}
return number;
}
return roundFun(val);
}
const BORDER_WIDTH_KEYWORD_MAP = {
thin: 1,
medium: 3,
thick: 5
};
function handleBorderWidth (val) {
return BORDER_WIDTH_KEYWORD_MAP[val] || handleNumber(val);
}
function handleBorder (val, platform, side) {
const res = [];
const valList = val.trim().split(/\s+/);
for (let i = 0; i < valList.length; i++) {
switch (i) {
case 0:
res.push({ property: `border${side}Width`, value: handleBorderWidth((valList[i])) });
break;
case 1:
res.push({ property: `border${side}Style`, value: valList[i] });
break;
case 2:
res.push({ property: `border${side}Color`, value: valList[i] });
break;
}
}
return res;
}
handleBorder('3px solid black');
// 为 number 保留 n 位小数,默认为 4
function roundFun (value, n = 4) {
return Math.round(value * Math.pow(10, n)) / Math.pow(10, n);
}
function handleNumber (val, fn = parseFloat) {
if (typeof val !== 'number') {
let number = fn(val);
if (isNaN(number)) {
return null;
}
// 保留小数点后 4 位
// 客户端接受数值精度 4 位即可,减少通信体积
number = roundFun(number);
// 支持vw/vh
if (val.endsWith('vh')) {
return number + 'vh';
}
if (val.endsWith('vw')) {
return number + 'vw';
}
return number;
}
return roundFun(val);
}
/**
* border 的简写样式
*/
const BORDER_WIDTH_KEYWORD_MAP = { 'thin': 1, 'medium': 3, 'thick': 5 };
const BORDER_STYLE_KEYWORD_MAP = { 'solid': 1, 'dotted': 1, 'dashed': 1 };
function getCSSPropertyBorderWidth(property, val) {
return { property: property, value: BORDER_WIDTH_KEYWORD_MAP[val] || handleNumber(val) };
}
function getCSSPropertyBorderStyle(property, val) {
return BORDER_STYLE_KEYWORD_MAP.hasOwnProperty(val)
? { property: property, value: val } : null;
}
function getCSSPropertyBorderColor(property, val) {
return { property: property, value: val };
}
const borderShorthand = [
{ property: 'borderWidth', parser: getCSSPropertyBorderWidth },
{ property: 'borderStyle', parser: getCSSPropertyBorderStyle },
{ property: 'borderColor', parser: getCSSPropertyBorderColor }
];
function consumeShorthandGreedilyViaLonghands(
shorthand,
valueTokenList
) {
const longhands = [];
for (const valueToken of valueTokenList) {
let foundLonghand = false;
for (let i = 0; !foundLonghand && i < shorthand.length; i++) {
// 已经匹配过的单个属性不再处理
if (longhands[i]) {
continue;
}
longhands[i] = shorthand[i].parser(shorthand[i].property, valueToken);
// 当前 token 匹配成功,结束内层循环,去匹配下一个 token
if (longhands[i]) {
foundLonghand = true;
}
}
// 任意一个 token 没有匹配成功,简写属性返回 null,比如:
// 1. valueTokenList 长度超出简写属性定义,超出的部分无法匹配成功
// 2. 长度小于等于定义,错误的 token 或者 相同属性的 token 也会导致匹配失败
if (!foundLonghand) {
return null;
}
}
const res = [];
for (let i = 0; i < shorthand.length; i++) {
if (longhands[i]) {
res.push(longhands[i]);
}
// na 下如果要声明属性默认值,最好的选择是不传递属性,减少数据体积,默认值由端上处理即可
// else {
// res.push({ property: shorthand[i].property, value: 'initial' });
// }
}
return res.length > 0 ? res : null;
}
function handleBorderShorthand(val) {
const valueTokenList = val.split(/\s+/);
return consumeShorthandGreedilyViaLonghands(borderShorthand, valueTokenList);
}
handleBorderShorthand('3px solid black');
简写属性取消机制
设置了简写属性之后再将简写属性设置为 unset
全部都返回 null