CocoaUI 的 CSS 樣式應用算法說明和源碼解析

jopen 9年前發布 | 12K 次閱讀 CSS

 

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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!