CocoaUI 的 CSS 样式应用算法说明和源码解析

jopen 8年前

 

W3C 规范中对 CSS 样式的应用算法有规定, 这个规范中的算法比较复杂, 简单来说, 就是根据 CSS 样式选择器中的不同类型的元素出现的次数来计算优先级, 如果某个节点同时命中多个 CSS 样式规则, 以高优先级的样式为准.

W3C 规范具体可以见这个文档: http://www.w3.org/TR/CSS2/cascade.html , "6.4.3 Calculating a selector's specificity" 一节.

例如下面的两条 CSS 样式规则和 HTML:

<style>  ul li .clz{color: #33f;}  li .clz{color: #f33;}  </style>    <ul>      <li><span class="clz">Hello World!</span></li>  </ul>

如果按照 W3C 规范来计算优先级, 那么会计算出:

第一条的优先级: a=0, b=0, c=1, d=2  第二条的优先级: a=0, b=0, c=1, d=1

那么, "Hello World!" 文字的颜色应该是蓝色(在现代浏览器中确实是蓝色). 但是, CocoaUI 做了一些改变, 因为这种计算优先级的算法略复杂, 不符合人直观的看法, 所以 CocoaUI 采用的出现的先后顺序算法, 后定义的样式的优先级更高并覆盖前面的定义. 所以, 对于 CocoaUI, 字体的颜色是红色的.

当然, 先来后到并不是 CocoaUI 应用 CSS 样式的唯一优先级, 因为有时更精准的规则, 我们直观地认为它的优先级应该更高. 所以, CocoaUI 定义了下面的优先级(从低到高):

  • 默认 CSS
  • 标签 CSS
  • 类选择器 CSS
  • ID 选择器 CSS
  • 内联 CSS(HTML 标签中通过 style 属性定义)
  • 动态修改的样式(包括修改 class)

这个优先级并不是凭空臆造出来的, 和 CSS 规范是基本相符的, 具体可以参考 Wiki .

前面说的是算法和流程, 那么具体到代码中应该怎么实现呢? 首先, 需要关注 IStyleDeclBlock 类.

IStyleDeclBlock 类是一种列表(数组)结构, 所以, 只要根据前面定义的优先级把各种样式添加进去, 当你从前往后遍历这个数组时, 依次应用(渲染)样式即可. 在解析 HTML/XML 时(类 IViewLoader), 按优先级顺序将样式已经加到 IStyleDeclBlock 列表里了.

类: IViewLoader  // 1. builtin(default) css  // 2. tagName css  // 3. class css  // 4. ID css  // 5. inline css  // $: dynamic set css  if(defaultCss){    [view.style set:defaultCss];  }  [view.style setTagName:tagName];  if(attributeDict){    NSString *class_ = [attributeDict objectForKey:@"class"];    if(class_ != nil){      NSMutableArray *ps = [NSMutableArray arrayWithArray:                  [class_ componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]];      [ps removeObject:@""];      for(NSString *clz in ps){        [view.style addClass:clz];      }    }    NSString *id_ = [attributeDict objectForKey:@"id"];    if(id_ != nil && id_.length > 0){      [_viewsById setObject:view forKey:id_];      [view.style setId:id_];    }    NSString *css = [attributeDict objectForKey:@"style"];    if(css){      [view.style set:css];    }  }

具体的样式渲染代码在 IStyle 类里:
类: IStyle  - (void)renderAllCss{    [self reset];    for(IStyleDecl *decl in _declBlock.decls){      if(decl.isId || decl.isClass || decl.isTagName){        IStyleSheet *sheet = _view.inheritedStyleSheet;        NSString *v = decl.val;        for(IStyleRule *rule in sheet.rules){          if([rule.selectors containsObject:v] && [rule match:_view]){            for(IStyleDecl *decl in rule.declBlock.decls){              [self applyDecl:decl baseUrl:rule.declBlock.baseUrl];            }          }        }      }else{        [self applyDecl:decl baseUrl:_declBlock.baseUrl];      }    }  }

因为 _declBlock 中的元素已经排好序, 所以我们直接遍历即可. 注意这段代码:

[rule.selectors containsObject:v]

假设 _declBlock 中一个名为 "clz" 的类(也就是当前的 DOM 节点 _view 定义了这个 class), 那么我们要先找出规则中包含 "clz" 的样式规则, 然后才判断是否匹配(match). 如果规则中不包含 "clz", 那就不要判断, 因为 match 方法并不区分匹配的类型, 而这里, 我们希望它只匹配特定的一种类型即可.

关于 match 的实现, 可以看这篇博客文章: ideawu - CSS 样式规则的匹配算法实现 .

CocoaUI 项目官方网站: http://www.cocoaui.com/ , 源码下载: https://github.com/ideawu/cocoaui