设计思路

为什么要设计一个CSS控制器?主要有以下几个原因:1、不想直接使用style操作元素的样式;2、不喜欢CSS/JS/TS文件分离;3、可以通过前端框架的高级功能(例如Vue的指令)执行一些更便捷的操作。

基于第三个原因举个简单的例子:例如在Vue中通过指令实现一个v-style指令,通过v-style="background: red"这样的方式直接帮你在元素上绑定一个匿名的class,你不用在意样式表的类名是如何取的,系统会帮你自动为元素绑定上类名,因此元素上也不会出现一个style属性。

基于此,你甚至可以通过你自己的加密算法将CSS样式加密,任何不熟悉你页面构造的人,将无法直接获取你的页面样式及结构。

设计实现

以下展示基本实现

class StyleControl {
  static styleElement: StyleControl | null

  private readonly styleElement: HTMLStyleElement

  constructor() {
    const styleElement = document.createElement("style");
    document.head.appendChild(styleElement);
    this.styleElement = styleElement;
  }

  static style(): StyleControl {
    if (this.styleElement) {
      return this.styleElement
    }
    this.styleElement = new StyleControl();
    return this.styleElement
  }

  private rulesMap(style: Record<string, string | number>) {
    const rules: string[] = []
    for (const [key, value] of Object.entries(style)) {
      rules.push(`${key}: ${value}`);
    }
    return rules;
  }

  insert(selector: string, style: Record<string, string | number> | string): number | void {
    let rules: string[] = []
    const sheet = this.styleElement.sheet;
    if (sheet) {
      if (typeof style === 'string') {
        rules.push(style);
      } else {
        rules = this.rulesMap(style)
      }
      return sheet.insertRule(`${selector} { ${rules.join(";")} }`, sheet.cssRules.length);
    }
  }

  insertKeyframes(selector: string, keyframes: Record<string, Record<string, string | number>>) {
    const rules: string[] = []
    const sheet = this.styleElement.sheet;
    if (sheet) {
      for (const [key, value] of Object.entries(keyframes)) {
        rules.push(key, `{ ${this.rulesMap(value).join(';')} }`)
      }
      return sheet.insertRule(`${selector} { ${rules.join(" ")} }`, sheet.cssRules.length);
    }
  }

  delete(index: number): void {
    const sheet = this.styleElement.sheet;
    if (sheet) {
      return sheet.deleteRule(index);
    }
  }
}

export default StyleControl;

参考资料