import computeLayout from "./css-layout"; import { getDefaultStyle, scalableStyles, layoutAffectedStyles } from "./style"; type LayoutData = { left: number, top: number, width: number, height: number }; type LayoutNode = { id: number, style: Object, children: LayoutNode[], layout?: LayoutData }; let uuid = 0; class Element { public static uuid(): number { return uuid++; } public parent: Element | null = null; public id: number = Element.uuid(); public style: { [key: string]: any } = {}; public computedStyle: { [key: string]: any } = {}; public lastComputedStyle: { [key: string]: any } = {}; public children: { [key: string]: Element } = {}; public layoutBox: LayoutData = { left: 0, top: 0, width: 0, height: 0 }; constructor(style: { [key: string]: any } = {}) { // 拷贝一份,防止被外部逻辑修改 style = Object.assign(getDefaultStyle(), style); this.computedStyle = Object.assign(getDefaultStyle(), style); this.lastComputedStyle = Object.assign(getDefaultStyle(), style); Object.keys(style).forEach(key => { Object.defineProperty(this.style, key, { configurable: true, enumerable: true, get: () => style[key], set: (value: any) => { if (value === style[key] || value === undefined) { return; } this.lastComputedStyle = this.computedStyle[key] style[key] = value this.computedStyle[key] = value // 如果设置的是一个可缩放的属性, 计算自己 if (scalableStyles.includes(key) && this.style.scale) { this.computedStyle[key] = value * this.style.scale } // 如果设置的是 scale, 则把所有可缩放的属性计算 if (key === "scale") { scalableStyles.forEach(prop => { if (style[prop]) { this.computedStyle[prop] = style[prop] * value } }) } if (key === "hidden") { if (value) { layoutAffectedStyles.forEach((key: string) => { this.computedStyle[key] = 0; }); } else { layoutAffectedStyles.forEach((key: string) => { this.computedStyle[key] = this.lastComputedStyle[key]; }); } } } }) }) if (this.style.scale) { scalableStyles.forEach((key: string) => { if (this.style[key]) { const computedValue = this.style[key] * this.style.scale; this.computedStyle[key] = computedValue; } }); } if (style.hidden) { layoutAffectedStyles.forEach((key: string) => { this.computedStyle[key] = 0; }); } } getAbsolutePosition(element: Element) { if (!element) { return this.getAbsolutePosition(this) } if (!element.parent) { return { left: 0, top: 0 } } const {left, top} = this.getAbsolutePosition(element.parent) return { left: left + element.layoutBox.left, top: top + element.layoutBox.top } } public add(element: Element) { element.parent = this; this.children[element.id] = element; } public remove(element?: Element) { // 删除自己 if (!element) { Object.keys(this.children).forEach(id => { const child = this.children[id] child.remove() delete this.children[id] }) } else if (this.children[element.id]) { // 是自己的子节点才删除 element.remove() delete this.children[element.id]; } } public getNodeTree(): LayoutNode { return { id: this.id, style: this.computedStyle, children: Object.keys(this.children).map((id: string) => { const child = this.children[id]; return child.getNodeTree(); }) } } public applyLayout(layoutNode: LayoutNode) { ["left", "top", "width", "height"].forEach((key: string) => { if (layoutNode.layout && typeof layoutNode.layout[key] === "number") { this.layoutBox[key] = layoutNode.layout[key]; if (this.parent && (key === "left" || key === "top")) { this.layoutBox[key] += this.parent.layoutBox[key]; } } }); layoutNode.children.forEach((child: LayoutNode) => { this.children[child.id].applyLayout(child); }); } layout() { const nodeTree = this.getNodeTree(); computeLayout(nodeTree); this.applyLayout(nodeTree); } } export default Element;