这里所说的“元素渲染”指的是使用 JavaScript 代码创建的 DOM 元素渲染。
元素的创建
在组件内部,尤其那些 HTML 结构复杂的组件元素,元素的创建都是采用 HTML 字符串拼接的方式实现的,而不是使用 HTML 模板,一方面是历史遗留问题,因为 Edge 主题是从 Pure 主题改造而来,采用一致的创建策略可以节约开发成本,另一方面是成本问题,使用模板创建自然就需要模板渲染引擎,LuLu UI 并没有 utils 中心的概念,出现模板引擎意味着组件的复杂度增加,权衡下来是不划算的。
Dialog 弹框元素比较特殊,因为无论是弹框的主体、标题、按钮等元素在开发过程中是经常需要进行额外控制的,因此,Dialog 弹框的关键元素均是采用 createElement() 方法创建的,这样,返回的 DOM 对象可以直接作为参数暴露在外。
和文档建立连接
组件元素创建后,需要和文档建立联系才能有对应的 UI 样式效果,纵观整个 Edge 主题,和文档建立联系的方式可以归为下面这几类。
- 预置 DOM 结构;
- 就地 DOM 创建;
- 根元素附加;
- 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
层级都是动态计算的,计算规则大致分为下面这两类:
- 兄弟元素间层级最大;
<body>
子元素层级最大;
为什么要让当前处于激活态的组件层级最高呢?
原因很简单,UI 组件的层级高低是根据场景变化的,例如一个提示浮层,可能被弹框覆盖,也可能是弹框内元素触发的,因此,提示浮层和弹框就不能设置固定的 z-index
值,否则总会出现某个场景的层级不是我们需要的。
以上就是 Edge 主题中关于组建渲染的介绍,核心就是三步,创建 DOM 结构,附加到页面,然后进行样式渲染。
本页贡献者:
zhangxinxu