2015-11-11

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

Views: 15434 | Add Comments

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!" 文字的颜色应该是蓝色的. 这个规则之间的优先级定义在文档中说得比较清楚, 简单翻译下就是:

CSS 样式规则优先级

  • HTML 标签中的 'style' 属性定义的内联CSS, a=1
  • 规则的选择器中, ID 每出现一次, b+=1
  • 规则的选择器中, Class 每出现一次, c+=1
  • 规则的选择器中, Tag Name 每出现一次, d+=1

简单来理解, a 就是整数的十亿位数, b 就是整数上的百万位数, c 就是整数上的千位数, d 就是整数上的个位数. 代码如下:

// 类: IStyleRule

for(NSString *key in selectors){
    unichar c = [key characterAtIndex:0];
    switch(c){
        case '#': // ID
            _weight += 1 * 1000 * 1000;
            break;
        case '.': // Class
            _weight += 1 * 1000;
            key = [key lowercaseString];
            break;
        default: // Tag
            _weight += 1;
            key = [key lowercaseString];
            break;
    }
}

样式来源匹配顺序

上面提到的是规则之间的优先级. 而 CSS 样式还要根据来源的不同来划分优先级, 根据来源划分优先级如下(从低到高):

  • 默认 CSS
  • 样式表 CSS(HTML 中通过 style 标签来定义)
  • 内联 CSS(HTML 标签中通过 style 属性定义)
  • 动态修改的样式

这个和 CSS 规范是相符的, 具体可以参考 Wiki.

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

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

// 类: IViewLoader
        
// 1. builtin(default) css
// 2. stylesheet(by style tag) css
// 3. inline css
// $: dynamically set css

// 1.
if(defaultCss){
    [view.style set:defaultCss];
}
// 2.
[view.style.declBlock addKey:@"@" value:@""];

if(attributeDict){
    // 3.
    NSString *css = [attributeDict objectForKey:@"style"];
    if(css){
        [view.style set:css];
    }
}

具体的样式渲染代码在 IStyle 类里:

// 类: IStyle
for(IStyleDecl *decl in _declBlock.decls){
    if([decl.key isEqualToString:@"@"]){
        IStyleSheet *sheet = _view.inheritedStyleSheet;
        if(sheet){
            for(IStyleRule *rule in sheet.rules){
                if([rule match:_view]){
                    for(IStyleDecl *decl in rule.declBlock.decls){
                        [self applyDecl:decl baseUrl:rule.declBlock.baseUrl];
                    }
                }
            }
        }
    }else{
        [self applyDecl:decl baseUrl:_declBlock.baseUrl];
    }
}

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

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

Related posts:

  1. 谁在使用 CocoaUI 框架?
  2. CocoaUI Viewer 开源了
  3. CocoaUI 框架开发 iOS 应用的一般步骤
  4. 基于 CocoaUI 的 iOS 应用 UI 热更新技术
  5. 流式布局的原理和代码实现
Posted by ideawu at 2015-11-11 16:26:18 Tags: ,

Leave a Comment