元素渲染

这里所说的“元素渲染”指的是使用 JavaScript 代码创建的 DOM 元素渲染。

元素的创建

在组件内部,尤其那些 HTML 结构复杂的组件元素,元素的创建都是采用 HTML 字符串拼接的方式实现的,而不是使用 HTML 模板,一方面是历史遗留问题,因为 Edge 主题是从 Pure 主题改造而来,采用一致的创建策略可以节约开发成本,另一方面是成本问题,使用模板创建自然就需要模板渲染引擎,LuLu UI 并没有 utils 中心的概念,出现模板引擎意味着组件的复杂度增加,权衡下来是不划算的。

Dialog 弹框元素比较特殊,因为无论是弹框的主体、标题、按钮等元素在开发过程中是经常需要进行额外控制的,因此,Dialog 弹框的关键元素均是采用 createElement() 方法创建的,这样,返回的 DOM 对象可以直接作为参数暴露在外。

和文档建立连接

组件元素创建后,需要和文档建立联系才能有对应的 UI 样式效果,纵观整个 Edge 主题,和文档建立联系的方式可以归为下面这几类。

  1. 预置 DOM 结构;
  2. 就地 DOM 创建;
  3. 根元素附加;
  4. Shadow DOM 创建;

基于上面的归类,特意整理了如下所示的表格,可以清晰地看出各个 UI 组件是如何和文档建立联系的。

组件名 预置 DOM 结构 就地 DOM 创建 根元素附加 Shadow DOM 创建
Button按钮 - - -
Input/Textarea输入框 - - -
radio/checkbox选择框 - - -
Progress进度条 - - -
Select下拉框 - -
Range范围选择 - - -
Color颜色选择 - -
Datetime日期选择 - -
Datalist数据列表 - -
Dialog弹框 (?) (?) -
Tip提示 - - -
ErrorTip出错提示 - - -
LightTip轻提示 - - -
Pagination分页 - -
Drop下拉 (?) - -
Tab切换 - - -
Validate表单验证 - - - -
Table列表 - - -
Form表单 - - -

下面简单介绍一下上面所提到的 4 种组件和文档建立连接的方式。

1. 预置 DOM 结构

指的是组件要想生效,需要在 HTML 中提前写好 DOM 结构。

例如按钮:

<button type="primary" is="ui-button">主按钮</button>

本组件(弹框和轻弹框除外)不提供任何创建按钮的方法,所有的按钮效果需要提前在页面中写好,所有的样式效果全部都是由 CSS 渲染完成。

2. 就地创建

所谓就地创建,就是原始 DOM 元素在哪里,JavaScript 创建的元素就在哪里。

具有代表性的 UI 组件就是 <select> 下拉组件,其创建的 DOM 结构就在 <select> 元素的后方。

原因在于,在所有的表单元素控件中,只有 <select> 元素是无法对样式进行精确的重置的,为了组件渲染时候的布局稳定性,就使用了在原地创建下拉框元素的方式。

HTML结构示意如下:

<!-- 元素下拉元素 -->
<select is="ui-select"></select>
<!-- JS 创建的 DOM 元素 -->
<div class="ui-select">
    <a href="javascript:" class="ui-select-button"></a>
    <div class="ui-select-datalist">
        <span class="ui-select-datalist-li disabled"></span>
    </div>
</div>

Dialog 弹框也有就地创建 DOM 的场景,具体描述为:如果 <dialog is="ui-dialog"> 提前在页面中写好,则此时弹框的主结构、标题、按钮等元素也是就地创建的。

3. 根元素附加

“根元素附加”指的是通过 JavaScript 创建的 DOM 元素是直接附加到文档的根元素下面的,在 LuLu UI 中,这个根元素就是 <body> 元素。

根元素附加多用在需要定位显示的 UI 组件上,包括时间选择器、颜色选择器、数据列表、弹框、各种提示效果、部分下拉效果。

// 示意
document.body.append(dialog);

由于组件元素作为<body> 子元素渲染,因此定位的时候受到的干扰和限制就小,这种限制包括层级、混合模式、溢出检测等。

4. Shadow DOM 创建

指的是自定义元素的内部细节全部使用 Shadow DOM 创建,在所有的 UI 组件中,只有 Pagination 分页组件使用了 Shadow DOM。

因为虽然 Edge 主题中的自定义元素很多,但是只有 <ui-pagination> 元素的内部 DOM 结构比较复杂,有较多的细节,其他的自定义元素内容简单且不固定,更多的是一种语法形式。

虽然只有 <ui-pagination> 使用了 Shadow DOM,似乎有违一致性原则,实际上,并没有,分页组件颜色、尺寸也是同样使用外部 CSS 进行设置的,和其他 UI 组件的样式渲染规则一致。

样式渲染

组件样式渲染分为两部分,CSS 渲染和 JS 渲染,其中,CSS 渲染几乎覆盖了所有的组件 UI,JS 参与样式渲染的部分比较有限。

CSS 样式渲染

Edge 主题采用了原生的 CSS 变量进行样式设置。

全局 CSS 变量全部设置在一个名为 variables.css 的文件中,如果大家希望改变 LuLu UI的整体样式,修改 variables.css 的文件中的尺寸、色值等参数就可以了。

:root {
    /* 基础颜色变量 */
    --ui-blue: #2a80eb;
    --ui-dark-blue: #0057c3;
    --ui-dark: #4c5161;
    --ui-gray: #a2a9b6;
    --ui-dark-gray: #b6bbc6;
    --ui-light: #f7f9fa;
    --ui-white: #ffffff;
    --ui-green: #1cad70;
    --ui-orange: #f59b00;
    --ui-red: #eb4646;
    /* hover的颜色 */
    --ui-list-hover: #f0f7ff;
    /* selected的颜色 */
    --ui-list-selected: #e0f0ff;
    /* disabled禁用色 */
    --ui-disabled: #ccd0d7;
    /* 边框颜色 */
    --ui-border: #d0d0d5;
    /* 深一点的边框颜色 */
    --ui-dark-border: #ababaf;
    /* 浅一点的边框颜色 */
    --ui-light-border: #ededef;
    /* 透明度 */
    --ui-opacity: .4;
    /* 圆角变量 */
    --ui-radius: 4px;
    /* 基础字号 */
    --ui-font: 14px;
    /* 动画时间 */
    --ui-animate-time: .2s;
    /* 基本尺寸单元 */
    --ui-line-height: 20px;
    --ui-component-height: 40px;
}

如果 LuLu UI 是通过 npm install 安装到项目中的,则可以通过重置的方式修改对应的 CSS 变量值,具体到某个组件的 CSS 变量的设置也是如此。例如:

body {
    --ui-blue: deepskyblue;                
}

需要注意的是,如果大家是单独引用的某个 UI 组件的 CSS 文件,例如:

<link rel="stylesheet" href="https://unpkg.com/lu2/theme/edge/css/common/ui/Dialog.css">

此时,Dialog.css 的色值并不是使用的全局 CSS 变量值,而是使用的后备色值,例如下面的 CSS 代码片段:

[is="ui-dialog"] {
    /* 如果变量 --ui-dark 未定义,会使用后面的色值 #4c5161 */
    color: var(--ui-dark, #4c5161);
    /* 如果变量 --ui-font 未定义,会使用后面的长度值 14px */
    font-size: var(--ui-font, 14px);
}

可以看到,之所以单独引用 UI 组件的 CSS 文件样式也是完好的,是因为颜色、字号、圆角大小等样式在设置的时候,都预留了后备属性值,保证了就算没有定义 CSS 变量,组件的样式也是完好的。

这就是 LuLu UI 中 CSS 变量的设计方法,保证某一个 UI 组件既能单独使用,又能成体系运行。

而单独使用的时候,对于某些样式的重置也是异常的简单,在组件任意祖先元素上设置对应的 CSS 变量值即可。

另外,还有一个名为 animate.css 的文件,这个 CSS 文件也是可选的,如果引用,部分 UI 组件在出现的时候,会有淡入淡出,或者上浮下沉小动画。

各个 CSS 文件位置和作用见下表。

CSS 文件 性质 说明
animate.css 可选 组件交互动画增强 CSS
color.css 可选 基础的颜色设置,定义了基本的颜色类名
variables.css 可选 定义了全局的 CSS 变量
form.css 工具合并 所有表单控件样式合集。
ui.css 工具合并 所有 CSS 合集,含 variables.css,不包括 animate.css 和 color.css
ui/*.css - 各个组件 CSS 文件,可独立引用

JS 参与的渲染

JS 参与渲染的部分主要下面这几点:

  • 绝对定位元素的定位计算,尺寸设置等;
  • 键盘访问的样式处理(需要引用 Keyboard.js 和 Keyboard.css);
  • z-index 层级计算处理;

这里对z-index 层级计算稍微展开介绍下。

在LuLu UI中,所有带有定位性质的元素(不包括 Select 下拉框)的z-index 层级都是动态计算的,计算规则大致分为下面这两类:

  1. 兄弟元素间层级最大;
  2. <body> 子元素层级最大;

为什么要让当前处于激活态的组件层级最高呢?

原因很简单,UI 组件的层级高低是根据场景变化的,例如一个提示浮层,可能被弹框覆盖,也可能是弹框内元素触发的,因此,提示浮层和弹框就不能设置固定的 z-index 值,否则总会出现某个场景的层级不是我们需要的。

以上就是 Edge 主题中关于组建渲染的介绍,核心就是三步,创建 DOM 结构,附加到页面,然后进行样式渲染。

本页贡献者:

zhangxinxu