Angular cdk Scrolling包给我们提供了一些指令和Service来应对滑动事件。推荐大家尽量去看官网上的介绍https://material.angular.io/cdk/scrolling/overview
想要在我们项目中使用cdk Scrolling功能,需要在我们模块里面 import {ScrollDispatchModule} from '@angular/cdk/scrolling';
一 cdkScrollable and ScrollDispatcher
cdkScrollable指令单独使用的时候没有特别的效果,cdkScrollable指令一般用来配合ScrollDispatcher Service使用。任何添加了cdkScrollable指令的视图元素都会注册到ScrollDispatcher里面去。然后可以在ScrollDispatcher里面去监听元素的滑动事件。
1.1 ScrollDispatcher常用方法介绍
export declare class ScrollDispatcher implements OnDestroy {
/**
* 订阅全局`scroll`和`resize`
*/
_globalSubscription: Subscription | null;
/**
* 注册到ScrollDispatcher里面的元素(添加了CdkScrollable指令的元素)
*/
scrollContainers: Map<CdkScrollable, Subscription>;
/**
* 注册CdkScrollable(我们不用管,我们在某个视图元素上添加CdkScrollable指令会自动注册的)
*/
register(scrollable: CdkScrollable): void;
/**
* 取消注册
*/
deregister(scrollable: CdkScrollable): void;
/**
* 可以去订阅任务一个注册CdkScrollable的scroll事件
*
* **Note:** 为了避免每次滚动都发送状态变化检测,内部采用的是NgZone.runOutsideAngular。所以如果你想每次都检测变化就采用NgZone.run的放
* 有疑问可以去搜下NgZone的使用
*/
scrolled(auditTimeInMs?: number): Observable<CdkScrollable | void>;
/**
* 监听elementRef视图元素的祖先元素有滚动事件,当然了对应的祖先元素需要添加CdkScrollable指令
* auditTimeInMs参数是为了防止事件发送过快,可以设置多长时间收一次事件
*/
ancestorScrolled(elementRef: ElementRef, auditTimeInMs?: number): Observable<CdkScrollable | void>;
/**
* elementRef添加了CdkScrollable指令的祖先元素
*/
getAncestorScrollContainers(elementRef: ElementRef): CdkScrollable[];
}
1.2 cdkScrollable and ScrollDispatcher使用
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
import {CdkScrollable, ScrollDispatcher} from '@angular/cdk/overlay';
@Component({
selector: 'app-cdk-scrolling',
template: `
<!-- 通过cdkScrollable指令配合ScrollDispatcher来监听节点的scrolling -->
<div cdkScrollable class="scrolling-parent">
<div #scrollingParent class="scrolling-item">item 1</div>
<div class="scrolling-item">item 2</div>
<div class="scrolling-item">item 3</div>
</div>
<!-- 这个div没有添加cdkScrollable指令,所以这个div的scrolling事件ScrollDispatcher获取不到 -->
<div class="scrolling-parent">
<div class="scrolling-item">item 1</div>
<div class="scrolling-item">item 2</div>
<div class="scrolling-item">item 3</div>
</div>
`,
styles: [`
.scrolling-parent {
height: 100px;
width: 200px;
border: 1px solid black;
padding: 8px;
overflow-y: auto;
}
.scrolling-item {
height: 50px;
}
`]
})
export class CdkScrollingComponent implements OnInit, AfterViewInit {
@ViewChild('scrollingParent')
childDiv: ElementRef;
constructor(private scrollDispatcher: ScrollDispatcher) {
}
ngOnInit() {
/**
* 监听所有ScrollDispatcher里面注册的CdkScrollable的scroll事件
*/
this.scrollDispatcher.scrolled().subscribe((scrollable: CdkScrollable) => {
if (scrollable) {
console.log('发生scroll了,來源于:');
console.log(scrollable.getElementRef().nativeElement);
}
});
}
ngAfterViewInit(): void {
/**
* 第二个参数auditTimeInMs表示事件延时多少秒发生
* 当祖先设置了cdkScrollable指令,在孩子里面也能抓到scrolling事件
*/
this.scrollDispatcher.ancestorScrolled(this.childDiv).subscribe((scrollable: CdkScrollable) => {
if (scrollable) {
console.log('祖先发生scroll了,來源于:');
console.log(scrollable.getElementRef().nativeElement);
}
});
// 获取ScrollDispatcher里面所有注册了scrolling的组件信息
console.log(this.scrollDispatcher.scrollContainers);
}
}
二 ViewportRuler
ViewportRuler也是cdk Scrolling里面提供的一个Service。用来测量浏览器的视图窗口信息。
2.1 ViewportRuler常用方法
export declare class ViewportRuler implements OnDestroy {
/**
* 获取浏览器窗口的宽度和高度
*/
getViewportSize(): Readonly<{
width: number;
height: number;
}>;
/**
* 获取浏览器窗口的rect信息(left、top、right、bottom)
*/
getViewportRect(): ClientRect;
/**
* 获取浏览器窗口的滑动位置(top、left)
*/
getViewportScrollPosition(): ViewportScrollPosition;
/**
* 监听浏览器窗口大小变化
*/
change(throttleTime?: number): Observable<Event>;
}
2.2 ViewportRuler使用实例
import {Component, OnInit} from '@angular/core';
import {ViewportRuler} from '@angular/cdk/overlay';
@Component({
selector: 'app-cdk-scrolling',
template: `
`
})
export class CdkScrollingComponent implements OnInit {
constructor(private viewPortRuler: ViewportRuler) {
}
ngOnInit() {
/**
* ViewportRuler 用来监听窗口的大小
*/
// { width, height }
console.log(this.viewPortRuler.getViewportSize());
// { bottom, height, left, right, top, width }
console.log(this.viewPortRuler.getViewportRect());
// { top, left }
console.log(this.viewPortRuler.getViewportScrollPosition());
// native resize event object
this.viewPortRuler.change().subscribe(resizeEvent => console.log(resizeEvent));
}
}
三 CdkVirtualScrollViewport
angular cdk 里面给提供了一个scrolling的组件CdkVirtualScrollViewport - cdk-virtual-scroll-viewport。CdkVirtualScrollViewport组件在list展示的时候非常有用,比如有我们list里面的item有1000项的时候。CdkVirtualScrollViewport 并不会直接给我们加载出1000项。只会加载部分项。一边滑动一边加载。如果有做过android的话,应该会知道ListView里面的回收机制。CdkVirtualScrollViewport组件做的也是类似的事情。
3.1 CdkVirtualScrollViewport组件主要方法介绍
export class CdkVirtualScrollViewport extends CdkScrollable implements OnInit, OnDestroy {
/**
* 这个是CdkFixedSizeVirtualScroll里面的属性,也是可以设置在CdkVirtualScrollViewport组件上的
* CdkVirtualScrollViewport组件里面每个item的大小(通过orientation来知道是高度还宽度)
*/
@Input()
get itemSize(): number { return this._itemSize; }
/**
* 这个是CdkFixedSizeVirtualScroll里面的属性,也是可以设置在CdkVirtualScrollViewport组件上的
* 多加载滚动范围之后多少像素的距离(最小值),比如CdkVirtualScrollViewport的高度是200px,minBufferPx
* 是100px; item有好多的情况下不用全部绘制出来。绘制200像素的内容就好了。
*/
@Input()
get minBufferPx(): number { return this._minBufferPx; }
/**
* 这个是CdkFixedSizeVirtualScroll里面的属性,也是可以设置在CdkVirtualScrollViewport组件上的
* 多加载滚动范围之后多少像素的距离(最大值)
*/
@Input()
get maxBufferPx(): number { return this._maxBufferPx; }
/**
* viewport 方向
*/
@Input() orientation: 'horizontal' | 'vertical' = 'vertical';
/**
* 活动过程中,当前可见第一项的index
*/
@Output() scrolledIndexChange: Observable<number> =
Observable.create((observer: Observer<number>) =>
this._scrollStrategy.scrolledIndexChange.subscribe(index =>
Promise.resolve().then(() => this.ngZone.run(() => observer.next(index)))));
/**
* 设置滑动位置
*/
scrollToOffset(offset: number, behavior: ScrollBehavior = 'auto'){}
/**
* 设置滑动的index
*/
scrollToIndex(index: number, behavior: ScrollBehavior = 'auto') {}
/**
* 测量可滑动的大小
*/
measureScrollOffset(from?: 'top' | 'left' | 'right' | 'bottom' | 'start' | 'end'): number {}
/**
* 测量所有item在一起的高度
*/
measureRenderedContentSize(): number{}}
上面主要方法介绍我把CdkVirtualScrollViewport组件和CdkFixedSizeVirtualScroll指令揉到一起去介绍了,因为它俩本来就是一起配合使用的。
3.2 CdkVirtualForOf指令
Selector: [cdkVirtualFor][cdkVirtualForOf]
CdkVirtualScrollViewport适用于list的情况,list循环的时候得配合CdkVirtualForOf指令来使用,CdkVirtualForOf的用法和ngFor的用法是一样的。CdkVirtualForOf指令是专门为CdkVirtualScrollViewport组件提供的。 就不用ngFor指令了。切记。如果想咱们平常一样用ngFor指令去循环,很多CdkVirtualScrollViewport组件里面特有的功能就用不了了。
3.3 CdkVirtualScrollViewport组件使用
想使用angular cdk Scrolling里面的功能先导入ScrollDispatchModule模块。
import {ScrollDispatchModule} from '@angular/cdk/scrolling';
3.3.1 CdkVirtualForOf指令的使用
主要是知道CdkVirtualForOf里面有哪些参数是可用的
import {Component, ViewChild} from '@angular/core';
import {CdkVirtualScrollViewport} from "@angular/cdk/scrolling";
@Component({
selector: 'app-virtual-scroll',
template: `
<!-- itemSize是每个item的高度 -->
<cdk-virtual-scroll-viewport #scrollComponent [itemSize]="18 * 7" class="example-viewport">
<!-- cdkVirtualFor的可以用参数如下 -->
<div *cdkVirtualFor="let item of items;
let index = index;
let count = count;
let first = first;
let last = last;
let even = even;
let odd = odd;" [class.example-alternate]="odd">
<div class="example-item-detail">Item: {{item}}</div>
<div class="example-item-detail">Index: {{index}}</div>
<div class="example-item-detail">Count: {{count}}</div>
<div class="example-item-detail">First: {{first ? 'Yes' : 'No'}}</div>
<div class="example-item-detail">Last: {{last ? 'Yes' : 'No'}}</div>
<div class="example-item-detail">Even: {{even ? 'Yes' : 'No'}}</div>
<div class="example-item-detail">Odd: {{odd ? 'Yes' : 'No'}}</div>
</div>
</cdk-virtual-scroll-viewport>
<button style="margin-top: 8px; margin-bottom: 8px; background-color: #007bff; color: red"
(click)="onButtonClick()">
点击跳转到第10个item
</button>
`,
styles: [`
.example-viewport {
height: 200px;
width: 200px;
border: 1px solid black;
}
.example-item-detail {
height: 18px;
}
`]
})
export class VirtualScrollComponent {
@ViewChild('scrollComponent')
private _scrollViewport: CdkVirtualScrollViewport;
/**
* CdkVirtualScrollViewport组件的数据源
*/
items = Array.from({length: 100000}).map((_, i) => `Item #${i}`);
/**
* 点击按钮的时候跳转到第10项
*/
onButtonClick() {
this._scrollViewport.scrollToIndex(10);
}
}
3.3.2 水平方向滚动的CdkVirtualScrollViewport组件
orientation属性的使用
import {ChangeDetectionStrategy, Component, ViewEncapsulation} from '@angular/core';
@Component({
selector: 'app-virtual-scroll-horizontal',
template: `
<div class="cdk-virtual-scroll-data-source-example">
<cdk-virtual-scroll-viewport orientation="horizontal" itemSize="50" class="example-viewport">
<div *cdkVirtualFor="let item of items" class="example-item">{{item}}</div>
</cdk-virtual-scroll-viewport>
</div>
`,
styles: [`
.cdk-virtual-scroll-data-source-example .example-viewport {
height: 200px;
width: 200px;
border: 1px solid black;
}
.cdk-virtual-scroll-data-source-example .example-viewport .cdk-virtual-scroll-content-wrapper {
display: flex;
flex-direction: row;
}
.cdk-virtual-scroll-data-source-example .example-item {
width: 50px;
height: 100%;
writing-mode: vertical-lr;
}
`],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class VirtualScrollHorizontalComponent {
/**
* CdkVirtualScrollViewport组件里面的数据源
*/
items = Array.from({length: 100000}).map((_, i) => `Item #${i}`);
}
关于angular cdk Scrolling里面咱们就先讲这么些,主要讲了cdkScrollable指令、ScrollDispatcher Service、ViewportRuler Service、CdkVirtualScrollViewport组件等一些很浅显的用法。如果大家有什么疑问欢迎留言。文章中涉及到的例子链接地址 https://github.com/tuacy/angular-cdk-study