VirtualScroll适用于页面上存在大量数据但是只有少量需要被同时渲染的情况,可以根据滚动条的高度来实时渲染需要显示的代码,原生的javascript实现起来比较困难,算法复杂,渲染耗费的性能得不偿失,但是在目前主流前端框架中可以非常简单的实现。
先上代码
vscroll.component.html:
<div #vscroll id='vscroll' style="height:400px;overflow: auto;width:400px">
<div #placeHolder [ngStyle]="{'height.px':items.length*25}" style='position:relative'>
<div #header style="height: 25px;position: fixed;background-color: white;z-index: 2;width:383px">
<div *ngFor="let header of headers" style="display: inline-block" [ngStyle]='{"width":(100/headers.length) +"%"}'>{{header}}</div>
</div>
<div body style="z-index: 1;width:400px">
<ng-container *ngFor="let item of items;let $index = index">
<div *ngIf="$index>=start&&$index<start+showNumber" style="height: 25px;" [ngStyle]="{'top.px':$index*25}" style="position: absolute;width:100%">
<div *ngFor="let header of headers" style="display: inline-block" [ngStyle]='{"width":(100/headers.length) +"%"}'>{{header + $index}}</div>
</div>
</ng-container>
</div>
</div>
</div>
vscroll.component.ts:
import { Component, OnInit, ViewChild, ElementRef, AfterViewInit, Input, OnDestroy } from '@angular/core';
import { fromEvent, pipe } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
@Component({
selector: 'app-vscroll',
templateUrl: './vscroll.component.html',
styleUrls: ['./vscroll.component.css']
})
export class VscrollComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('placeHolder') placeHolder: ElementRef;
@ViewChild('vscroll') vscroll: ElementRef;
@Input() showNumber: number;
public items = [];
public headers = ['index', 'username', 'pwd'];
public start: number;
private timer;
constructor() {
for (let i = 0; i < 1000; i++) {
this.items.push(i);
}
this.showNumber = this.showNumber ? this.showNumber : 16;
this.start = 0;
}
ngOnInit() {
}
ngAfterViewInit(): void {
this.updateShow();
fromEvent(document.getElementById('vscroll'), 'scroll').pipe(throttleTime(200)).subscribe((e) => {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
this.timer = setTimeout(() => {
this.updateShow();
}, 201);
} else {
this.timer = setTimeout(() => {
this.updateShow();
}, 201);
}
});
}
ngOnDestroy(): void {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
updateShow() {
this.start = this.vscroll.nativeElement.scrollTop / 25;
}
}
实战效果(没做美化,相当的丑)
DOM加载情况
原理很简单,先利用placeholder这个div来模拟整个列表全部显示的大小,再根据absolute-relative定位将每一条数据通过top来定位在placeholder上。
最后根据ngFor循环的index以及整个vscroll的scrollTop的来判断哪些数据在显示区域内将之显示,这样dom只需要加载并渲染需要的节点,从而优化性能。
不过这里没有直接绑定scroll事件来触发更新显示窗体的事件,这是为了使用RxJS的throttle功能进行节流操作,每200ms只接受一次scroll事件,来节省scroll事件的开销,如果在一个throttle周期内没有发生第二次scroll事件,那么将之定位到当前位置。
这里我同时也试过debounce,虽然debounce不需要额外设置一个timeout,但是执行的时候会有明显的延迟感除非将debounce时间设置到很短,那样就没有节流的意义了。
如果将文件里一些数字常量换成@Input,这个就是一个足够使用的轻量级组件了,最近的几天我会尝试着去增加他的功能。
网友评论