angularflexboxdebouncingsplitter

Angular splitter behaving glitchy


I'm building a Splitter component in angular, using the @angular/cdk/portals. Everything works as expected, but I'm still having trouble with the resizing of the splitter. When moving the thumb, the offset increases for no apparent reason

I've created a complete reproduction here. The project code is hosted here. And a live demo can be found here.

The splitter consists of panels inside a flexbox:

<div class="d-flex w-100" [class]="splitter-horizontal">
    <ng-content></ng-content>
    <ng-container *ngFor="let panel of (panels$ | async); let index = index; let last = last">
        <ng-container *bsLet="(widthStyles$ | async) as widthStyles">
            <div class="split-panel overflow-hidden" #splitPanel [style.width]="widthStyles | bsElementAt:index">
                <ng-template [cdkPortalOutlet]="panel.portal"></ng-template>
            </div>
        </ng-container>
        <div class="divider" (mousedown)="startResize($event, index, index + 1)" *ngIf="!last"></div>
    </ng-container>
</div>

startResize will compute the rendered widths once, and replace the width: 100% with the computed width for each panel:

const sizes = this.splitPanels
    .map((sp) => {
        const styles = window.getComputedStyle(sp.nativeElement);
        return styles.width;
    })
    .map((size) => size.slice(0, -2))
    .map((size) => parseFloat(size));

this.previewSizes$.next(sizes);

And the widthStyles$ are coming from the previewSizes$:

this.widthStyles$ = combineLatest([this.orientation$, this.previewSizes$, this.panels$])
    .pipe(map(([orientation, previewSizes, panels]) => {
        if (previewSizes) {
            return [...Array(panels.length).keys()].map((v, i) => {
                if (i < previewSizes.length) {
                    return previewSizes[i] + 'px';
                } else {
                    return '100%'; // Equal width panels
                }
            });
        } else {
            return Array(panels.length).map((v, i) => '100%');
        }
    }));

On MouseMove, I'm updating a BehaviorSubject:

@HostListener('document:mousemove', ['$event'])
onMouseMove(ev: MouseEvent) {
    this.mousePosition$.next({ x: ev.offsetX, y: ev.offsetY });
}

And the rest of startResize is meant to update the previewSizes$:

this.mousePosition$.pipe(delay(5), takeUntil(this.stopResize$)).subscribe((mousePosition) => {
    const deltaX = mousePosition!.x - this.operation.startPosition.x;
    const sx = Array.from(this.operation.sizes);
    sx[this.operation.indexBefore] = this.operation.sizes[this.operation.indexBefore] + deltaX;
    sx[this.operation.indexAfter] = this.operation.sizes[this.operation.indexAfter] - deltaX;
    this.previewSizes$.next(sx);
});

For some reason the splitter is still growing faster than I'm moving my mouse. I've already tried using debounce() which is why I created the mousePosition$ BehaviorSubject in the first place.

What am I doing wrong?


Solution

  • Okay, I just had to use ev.clientX instead of ev.offsetX and the problem was fixed