Skip to content

Commit

Permalink
feat: avoid layouts thrashing (#3764)
Browse files Browse the repository at this point in the history
  • Loading branch information
bytemain authored Sep 2, 2024
1 parent 9c91ec1 commit 8220550
Show file tree
Hide file tree
Showing 34 changed files with 880 additions and 380 deletions.
114 changes: 114 additions & 0 deletions packages/core-browser/__tests__/dom/fastdom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { fastdom } from '@opensumi/ide-core-browser';

/**
* 用于 mock requestAnimationFrame 的表现,可以手动触发动画帧
*/
class AnimationFrameController {
private callbacks: Array<() => void> = [];
private currentFrame: number = 0;
private running: boolean = false;

constructor() {
this.run = this.run.bind(this);
}

/**
* 触发动画帧
*/
public run() {
if (this.running) {
return;
}
this.running = true;
this.callbacks.forEach((callback) => {
callback();
});
this.running = false;
this.currentFrame++;
}

/**
* 注册回调函数
* @param callback
*/

public register(callback: () => void) {
this.callbacks.push(callback);
}

/**
* 注销回调函数
* @param callback
*/

public unregister(callback: () => void) {
const index = this.callbacks.indexOf(callback);
if (index !== -1) {
this.callbacks.splice(index, 1);
}
}

/**
* 获取当前的动画帧
*/
public getCurrentFrame() {
return this.currentFrame;
}
}

describe('fastdom', () => {
it('should measure', () => {
const animationFrameController = new AnimationFrameController();

let originalAnimationFrame = global.requestAnimationFrame;
let originalCancelAnimationFrame = global.cancelAnimationFrame;

global.requestAnimationFrame = (callback) => {
animationFrameController.register(callback);
return 1;
};

global.cancelAnimationFrame = () => {};
let count = 0;
fastdom.measure(() => {
count++;
expect(count).toBe(1);

fastdom.measure(() => {
// will run on current frame
count++;
expect(count).toBe(3);
});

fastdom.measureAtNextFrame(() => {
// will run on next frame
count++;
expect(count).toBe(4);

fastdom.measure(() => {
count += 2;
expect(count).toBe(6);
});
fastdom.mutate(() => {
count += 3;
expect(count).toBe(9);
});
});
});

fastdom.mutate(() => {
count++;
expect(count).toBe(2);
});

animationFrameController.run();
animationFrameController.run();
expect(count).toBe(9);
animationFrameController.run();
animationFrameController.run();
expect(count).toBe(9);

global.requestAnimationFrame = originalAnimationFrame;
global.cancelAnimationFrame = originalCancelAnimationFrame;
});
});
1 change: 1 addition & 0 deletions packages/core-browser/src/bootstrap/app.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function App(props: AppProps) {
lastFrame = null;
allSlot.forEach((item) => {
eventBus.fire(new ResizeEvent({ slotLocation: item.slot }));
eventBus.fireDirective(ResizeEvent.createDirective(item.slot));
});
});
};
Expand Down
15 changes: 8 additions & 7 deletions packages/core-browser/src/components/layout/split-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export const SplitPanel: React.FC<SplitPanelProps> = (props) => {
(location?: string) => {
if (location) {
eventBus.fire(new ResizeEvent({ slotLocation: location }));
eventBus.fireDirective(ResizeEvent.createDirective(location));
}
},
[eventBus],
Expand Down Expand Up @@ -332,20 +333,19 @@ export const SplitPanel: React.FC<SplitPanelProps> = (props) => {
if (rootRef.current) {
splitPanelService.setRootNode(rootRef.current);
}
const disposer = eventBus.on(ResizeEvent, (e) => {
if (e.payload.slotLocation === id) {
childList.forEach((c) => {
fireResizeEvent(getProp(c, 'slot') || getProp(c, 'id'));
});
}
const disposer = eventBus.onDirective(ResizeEvent.createDirective(id), () => {
childList.forEach((c) => {
fireResizeEvent(getProp(c, 'slot') || getProp(c, 'id'));
});
});
return () => {
disposer.dispose();
};
}, []);

const renderSplitPanel = React.useMemo(() => {
const { minResize, flexGrow, minSize, maxSize, savedSize, defaultSize, flex, noResize, slot, ...rest } = props;
const { minResize, flexGrow, minSize, maxSize, savedSize, defaultSize, flex, noResize, slot, headerSize, ...rest } =
props;

delete rest['resizeHandleClassName'];
delete rest['dynamicTarget'];
Expand All @@ -364,6 +364,7 @@ export const SplitPanel: React.FC<SplitPanelProps> = (props) => {
data-max-size={maxSize}
data-saved-size={savedSize}
data-default-size={defaultSize}
data-header-size={headerSize}
data-flex={flex}
data-flex-grow={flexGrow}
data-no-resize={noResize}
Expand Down
Loading

1 comment on commit 8220550

@opensumi
Copy link
Contributor

@opensumi opensumi bot commented on 8220550 Sep 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 Next publish successful!

3.2.6-next-1725270864.0

Please sign in to comment.