likes
comments
collection
share

业务实践篇5:多种样式适配的经验

作者站长头像
站长
· 阅读数 48

背景:项目第一版已经做好了,后面发现这个项目需要适配白色主题和暗黑主题,第一版是以白色主题为主的,前期也并没有以颜色值变量的方式开发。经过一周多的打磨(很烦),完成了项目的两种样式的适配。本文主要介绍本次适配的一些经验。

难点

1.第一版项目直接使用颜色值编写样式,多种样式适配必须要修改这些颜色值为动态的。

2.颜色值变量除了css,还有在js层面使用的。

技术要点

  • CSS 变量

常用的动态的样式是使用CSS变量var(),var() 函数用于插入 CSS 变量的值,语法是var(name, value),name以(--)开头,value为默认值。基本使用如下:

/* 定义CSS变量 */
:root {
  --blue: #1e90ff;
  --white: #ffffff;
}

/* 应用CSS变量 */
.a {
  color: var(--blue);
  background-color: var(--white);
  padding: 15px;
}
  • 动态插入CSS变量定义

项目要求是点击切换按钮,实时变更主题,这里采用的是动态变更这些CSS变量。在最上层组件通过脚本设置CSS变量,可动态变更定义的CSS变量。基本使用如下:

dom.style.setProperty(`--${name}`, value);

技术要点解决了,那接下来就是实际操作了。

实际操作

定义样式token

每套主题都有对应场景的颜色值,比如主题色,背景色,分割线颜色等等,为了统一,这里采取分别对两套主题定义各自对应token对象。比如

const lightTokens = {
  colorPrimary:'#1e90ff',
  bgColor:'#fff',
  // ...
}

const darkTokens = {
  colorPrimary:'#1e90ff',
  bgColor:'#000',
  // ...
}

然后切换不同的主题时,动态使用dom.style.setProperty(--${name}, value);在最上层注入这些变量

 for (let key in token) {
  rootDom.style.setProperty(`--${key}`, (token as any)[key]);
}

这样的处理的好处时,token对象也可用于js层面使用。

替换已有的颜色值

例如以下样例,要将定义的颜色值改为使用var的方式。

.a {
  color:#1e90ff;
  background-color: #fff;
}

/* 改 */
.a {
  color:var(--colorPrimary,#1e90ff);
  background-color: var(--bgColor,#fff);
}

如果一个个文件查找替换过去,这工作量够呛,而且还需要比对颜色值对应哪个变量名,既然这是一个重复的工作,那是否可以考虑使用脚本来替代。

思路是主要匹配css的颜色值,在lightToken中比对属于哪个变量,然后替换var的形式。

const lightTokens = {
  colorPrimary:'#1e90ff',
  bgColor:'#fff',
  // ...
}

function solveFileContent(filePath) {
  console.log('正在扫描文件', filePath);
  let data = fs.readFileSync(filePath).toString();
  // 定义匹配颜色值的正则
  const reg = new RegExp(/(?<!var(.*?)#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?=;)/);
  let result = data.match(reg);
  let dataChange = false;
  let index = 0;
  let off = 0;
  while (result) {
    if (result?.[0]) {
      const mat = Object.keys(lightToken).find(
        (v) => lightToken[v].toLowerCase() === result[0].toLowerCase(),
      );
      // 存在则说明需要替换为样式变量
      if (mat) {
        const newStr = `var(--${mat},${result[0]})`;
        data =
          data.substring(0, index + result.index) +
          newStr +
          data.substring(index + result.index + result[0].length);
        off = newStr.length;
        dataChange = true;
      } else {
        // 不在lightToken中定义的变量,不进行替换
        off = result[0].length;
        const rowTag = new RegExp(/\n/g);
        const rowTagResult = data
          .substring(0, index + result.index)
          .match(rowTag);
        console.log(
          `[${rowTagResult.length + 1}] ${
            result[0]
          } 满足匹配规则但不在定义内,请检查!`,
        );
      }
    }
    index = index + result.index + off;
    result = data.substring(index).match(reg);
  }
  if (dataChange && args[1] === 'true') {
    console.log('******修改文件开始******************************');
    fs.writeFile(filePath, data, function (err) {
      if (err) throw err;
      console.log(`${filePath} 该文件已经被修改`);
      console.log('******修改文件结束****************************');
    });
  }
}

以上代码是读取对应文件内容,通过正则匹配符合的颜色值,然后替换成var()形式,最后重写文件即可。

接下来只需要一个扫描系统文件的函数就行了。

const fs = require('fs');
const path = require('path');
const args = process.argv.slice(2);
const filePath = path.join(__dirname, '../', args[0]); // 此路径根据实际情况调整

console.log('正在扫描路径', filePath);

function solveFileOrDir(filePath) {
  const fileStat = fs.statSync(filePath);
  if (fileStat.isDirectory()) {
    try {
      const pathList = fs.readdirSync(filePath);
      for (let i = 0; i < pathList.length; i++) {
        solveFileOrDir(path.join(filePath, pathList[i]));
      }
    } catch (e) {
      console.log(filePath, e);
      return;
    }
  } else {
    if (filePath.indexOf('.less') > -1) {
    	solveFileContentFontSize(filePath);
    }
  }
}

以上代码是输入一个路径,会递归遍历,然后找到less文件进行一个批量替换。接着使用node执行该脚本,让他跑一会儿,就完成了所有变量的替换了。

$node src true

node src truesrc是系统代码文件的路径,true表示同时会修改原文件。

对于js层面的变量,由于使用规则比较杂,量也比较少,所以主要采用人工的方式来调整,其实可以用上面的脚本进行扫描替换。

拓展:修改全局字号px为rem

又突然有个需求,为了字号响应式变化,要把定义的字号改为rem,但目前全局使用的都是px。因为px和rem有一定的换算规则,所以不能直接批量替换。

这时候可以接用上面的脚本,只要修改具体对文件的操作就行了。

function solveFileContentFontSize(filePath) {
  console.log('正在扫描文件', filePath);
  let data = fs.readFileSync(filePath).toString();
  const reg = new RegExp(/(?<=font-size:\s).*(?=;)/);
  let result = data.match(reg);
  let dataChange = false;
  let index = 0;
  let off = 0;
  while (result) {
    if (result?.[0]) {
      let newStr = result[0];
      if (newStr.indexOf('rem') === -1) {
        const fontSizeNum = result[0].substring(0, result[0].length - 2);
        newStr = `${(Number(fontSizeNum) / 14).toFixed(2)}rem`;
      }
      data =
        data.substring(0, index + result.index) +
        newStr +
        data.substring(index + result.index + result[0].length);
      off = newStr.length;
      dataChange = true;
    }
    index = index + result.index + off;
    result = data.substring(index).match(reg);
  }
  if (dataChange && args[1] === 'true') {
    console.log('******修改文件开始******************************');
    fs.writeFile(filePath, data, function (err) {
      if (err) throw err;
      console.log(`${filePath} 该文件已经被修改`);
       console.log('******修改文件结束****************************');
    });
  }
}

以上代码是匹配font-size属性,然后根据px和rem的换算规则替换成rem即可。

总结:本文是一次业务实践的记录,通过分析工作方式,思考重复性工作是否有更加有效实现方式,使用脚本来替代人工修改,还可以避免一些人工错误。

转载自:https://juejin.cn/post/7244705506048688185
评论
请登录