Skip to content

Commit

Permalink
feat: add fragment to support batch operations
Browse files Browse the repository at this point in the history
  • Loading branch information
Aarebecca committed Oct 18, 2024
1 parent b2d71a7 commit 448fad0
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 156 deletions.
26 changes: 15 additions & 11 deletions __tests__/demos/perf/rect.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { Rect, Group, runtime } from '@antv/g';
import { Rect, Group, Fragment } from '@antv/g';
import type { Canvas } from '@antv/g';

export async function rects(context: { canvas: Canvas }) {
const { canvas } = context;

await canvas.ready;

const group1 = new Group({});
const group2 = group1.appendChild(new Group({}));
canvas.appendChild(group2);
const group1 = canvas.appendChild(new Group({ id: 'group1' }));
const group2 = group1.appendChild(new Group({ id: 'group2' }));

console.time('render');

const fragment = new Fragment();

for (let i = 0; i < 10_0000; i++) {
const group = new Group({
style: {
transform: [['translate', Math.random() * 640, Math.random() * 640]],
},
});
const group = fragment.appendChild(
new Group({
id: `group-${i}`,
style: {
transform: [['translate', Math.random() * 640, Math.random() * 640]],
},
}),
);

group.appendChild(
new Rect({
Expand All @@ -30,10 +34,10 @@ export async function rects(context: { canvas: Canvas }) {
},
}),
);

group2.appendChild(group);
}

group2.appendChild(fragment);

canvas.addEventListener(
'rerender',
() => {
Expand Down
38 changes: 22 additions & 16 deletions packages/g-lite/src/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
getHeight,
getWidth,
isBrowser,
isInFragment,
raf,
} from './utils';

Expand Down Expand Up @@ -694,36 +695,41 @@ export class Canvas extends EventTarget implements ICanvas {
}
}

mountChildren(parent: DisplayObject) {
mountChildren(
child: DisplayObject,
skipTriggerEvent: boolean = isInFragment(child),
) {
if (this.inited) {
if (!parent.isConnected) {
parent.ownerDocument = this.document;
parent.isConnected = true;

if (parent.isMutationObserved) {
parent.dispatchEvent(mountedEvent);
} else {
mountedEvent.target = parent;
this.dispatchEvent(mountedEvent, true);
if (!child.isConnected) {
child.ownerDocument = this.document;
child.isConnected = true;

if (!skipTriggerEvent) {
if (child.isMutationObserved) {
child.dispatchEvent(mountedEvent);
} else {
mountedEvent.target = child;
this.dispatchEvent(mountedEvent, true);
}
}
}
} else {
console.warn(
"[g]: You are trying to call `canvas.appendChild` before canvas' initialization finished. You can either await `canvas.ready` or listen to `CanvasEvent.READY` manually.",
'appended child: ',
parent.nodeName,
child.nodeName,
);
}

// recursively mount children
parent.childNodes.forEach((child: DisplayObject) => {
this.mountChildren(child);
child.childNodes.forEach((c: DisplayObject) => {
this.mountChildren(c, skipTriggerEvent);
});

// trigger after mounted
if (parent.isCustomElement) {
if ((parent as CustomElement<any>).connectedCallback) {
(parent as CustomElement<any>).connectedCallback();
if (child.isCustomElement) {
if ((child as CustomElement<any>).connectedCallback) {
(child as CustomElement<any>).connectedCallback();
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions packages/g-lite/src/display-objects/Fragment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Shape } from '../types';
import { DisplayObject } from './DisplayObject';

/**
* 节点片段,用于包裹多个节点并批量操作
*
* 向 Fragment 或者根节点为 Fragment 的节点添加子节点时,不会触发渲染和事件。
*
* 当 Fragment 被挂载到 Canvas 中的节点时,其子节点会一次性被渲染,并触发合并事件。
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
*/
export class Fragment extends DisplayObject {
constructor() {
super({ type: Shape.FRAGMENT });
}
}
1 change: 1 addition & 0 deletions packages/g-lite/src/display-objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './Circle';
export * from './CustomElement';
export * from './DisplayObject';
export * from './Ellipse';
export * from './Fragment';
export * from './Group';
export * from './HTML';
export * from './Image';
Expand Down
4 changes: 2 additions & 2 deletions packages/g-lite/src/dom/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,9 @@ export class Element<
this.forEach((object) => {
if (object !== this && filter(object as E)) {
target = object as E;
return true;
return false;
}
return false;
return true;
});
return target;
}
Expand Down
19 changes: 13 additions & 6 deletions packages/g-lite/src/dom/Node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,20 @@ export abstract class Node extends EventTarget implements INode {
return temp;
}

/**
* iterate current node and its descendants
* @param callback - callback to execute for each node, return false to break
* @param assigned - whether to iterate assigned nodes
*/
forEach(callback: (o: INode) => void | boolean, assigned = false) {
if (!callback(this)) {
(assigned ? this.childNodes.slice() : this.childNodes).forEach(
(child) => {
child.forEach(callback);
},
);
const result = callback(this);

if (result !== false) {
const nodes = assigned ? this.childNodes.slice() : this.childNodes;
const length = nodes.length;
for (let i = 0; i < length; i++) {
nodes[i].forEach(callback);
}
}
}
}
Loading

0 comments on commit 448fad0

Please sign in to comment.