问题
Prerequisite: cdk draggable elements inside a nested scrollable div (see the example https://stackblitz.com/edit/angular-7y19nm?file=app/cdk-drag-drop-sorting-example.html)
How to reproduce: Start dragging an item -> scroll the page -> drag item a bit more when not scrolling
Effect: item placeholder stays in wrong place and it's basically impossible to drag item anywhere outside the viewport.
<div style="height: 100vh; overflow-y: auto">
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let movie of movies" cdkDrag>{{movie}}</div>
</div>
</div>
回答1:
I've searched for this issue in the Angular components' official Github repository and I have found the following topics:
https://github.com/angular/components/issues/13588
https://github.com/angular/components/issues/16535
There are different solutions depending on the version that you use: Angular 9+ (works also with Angular 10) or Angular 8:
Angular 9+ (works also Angular 10)
From version 9.1.0, the scrolling of the parent element is supported by setting the cdkScrollable
directive to it.
So, for v9.1.0 and up, the following code should work:
<div style="height: 100vh; overflow-y: auto" cdkScrollable>
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
<div class="example-box" *ngFor="let movie of movies" cdkDrag>{{movie}}</div>
</div>
</div>
Stackblitz demo:
https://stackblitz.com/edit/angular-swaqkk-yjiz7r (uses v10.0.1)
https://stackblitz.com/edit/angular-vszdat (uses v9.2.4)
Angular 8
From version 8.1.0, the scrolling was enabled, but only for the cdkDropList itself or the viewport (for performance reasons). So there are two solutions available:
- We can set the fixed height and the
overflow: scroll
to thecdkDropList
element:
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)" style="height: 100vh; overflow-y: auto">
<div class="example-box" *ngFor="let movie of movies" cdkDrag>{{movie}}
</div>
</div>
Stackblitz demo:
https://stackblitz.com/edit/angular-avezy6
- If we can't make the
cdkDropList
scrollable and there is a parent element that should scroll (like the situation in the question), I've adapted a solution found here (https://github.com/angular/components/issues/16677#issuecomment-562625427): we can use a custom directivecdkDropListScrollContainer
, that will be set on thecdkDrag
elements. This directive will take as aInput
the reference to the parent element that should scroll:
<div class="example-container" style="height: 500px; overflow-y: auto" #scrollContainer>
<div cdkDropList class="example-list" (cdkDropListDropped)="drop($event)">
<div
class="example-box"
*ngFor="let movie of movies"
cdkDrag
[cdkDropListScrollContainer]="scrollContainer">
{{movie}}
</div>
</div>
</div>
The code for the directive is:
import { Directive, Input, ElementRef } from "@angular/core";
import { CdkDrag } from "@angular/cdk/drag-drop";
@Directive({
selector: "[cdkDropListScrollContainer]"
})
export class CdkDropListScrollContainerDirective {
@Input("cdkDropListScrollContainer") scrollContainer: HTMLElement;
originalElement: ElementRef<HTMLElement>;
constructor(cdkDrag: CdkDrag) {
cdkDrag._dragRef.beforeStarted.subscribe(() => {
const cdkDropList = cdkDrag.dropContainer;
if (!this.originalElement) {
this.originalElement = cdkDropList.element;
}
if (this.scrollContainer) {
const element = this.scrollContainer;
cdkDropList._dropListRef.element = element;
cdkDropList.element = new ElementRef<HTMLElement>(element);
} else {
cdkDropList._dropListRef.element = cdkDropList.element.nativeElement;
cdkDropList.element = this.originalElement;
}
});
}
}
Stackblitz demo: https://stackblitz.com/edit/angular-jkuqhg
来源:https://stackoverflow.com/questions/57755127/angular-cdk-issue-with-scrolling-and-dragging-element-inside-nested-scrollable