Angular实现虚拟滚动多选下拉框笔记

要求:

实现一个angular多选下拉框组件,当有超过2000个选项时,滑动/挑选/全选均不卡。

正篇:

为了方便,这里不考虑扩展性,所以规定下拉框展开只显示7行数据,行高27px

约定:input为一个叫做list的SelectItem[],SelectItem大概定义如下:

export class SelectItem {    
    public label: string;    
    public value: string;    
    public disabled?: boolean; // 某些选项禁用    
    public groupKey?: string; // 给选项分组用    
    public groupHead?: string; // 显示选项组    
    public checked?: boolean; // 是否打勾    
    public tempChecked?: boolean; // 是否临时打勾(用户要求,自动保存选项的话不需要)    
    public originalCheckStatus?: boolean; // 放弃保存(用户要求,自动保存选项的话不需要)
}复制代码

每次我们给用户看到的选项只有6个,所以当整个数组进来时我们最少只用截取6个;又因为规定每条高度27px,所以包含选项的框162px

this.actualList = this.list.slice(0, 8); // 多截两个当缓冲复制代码

.list-container {height: 162px;}复制代码

所以在html画出来时用户就刚好看见6个 (trackBy是额外的优化)

<div class="list-container">
    <div *ngFor="let item of actualList; trackBy: trackByValue" class="inner-option">
        <label>
            <input type="checkbox" [(ngModel)]="item.checked">
            <span>{{item.label}}</span>
        </label>
    </div>
<div>复制代码

可这个时候滑动条是满的不能滑动,我们需要给这个面板一个假的高度把面板撑开

<div class="list-container">
    <div [style.height]="scrollHeight">
        <div *ngFor="let item of actualList; trackBy: trackByValue" class="inner-option">
            <label>
                <input type="checkbox" [(ngModel)]="item.checked">
                <span>{{item.label}}</span>
            </label>
        </div>
    </div>
</div>复制代码

this.scrollHeight = this.list.length * 27;复制代码

.list-container {
    height: 162px;
    overflow: auto; // 当然你要让这个外层溢出后加滚动
}复制代码

这样你就得到了一个看起来可以滚动的但是其实只有第一页的下拉框。然后你要做的就是在用户滚动之后动态的更新actualList,这里我们可以用angular封装好的scroll event:

<div class="list-container" (scroll)="onScroll($event)">
    <div [style.height]="scrollHeight">
        <div *ngFor="let item of actualList; trackBy: trackByValue" class="inner-option">
            <label>
                <input type="checkbox" [(ngModel)]="item.checked">
                <span>{{item.label}}</span>
            </label>
        </div>
    </div>
</div>复制代码

public onScroll(e) {
    const firstIndex = Math.floor(e.target.scrollTop / 27);
    const secondIndex = firstIndex + 8;
    this.actualList = this.list.slice(firstIndex, secondIndex);
}复制代码

以上虽然你是更新了可以显示的实际list,但是还没有在面板上显示出来,因为这个actualList被你划上去了,所以接下来需要在滚动时动态的把这一块往下/上移,这里我们用translateY

public onScroll(e) {
...
this.translateY = `translateY(${firstIndex * 27}px)`;
}复制代码

<div>
...
    <div *ngFor=....class="inner-option" [style.transform]="translateY">
...
</div>复制代码

其实到这里核心的整个虚拟滚动的列表就制作完成了,剩下的工作包括:

  1. 并不是每一点滚动都需要重新计算actualList的,只有最上面的那个滚出去了之后才需要重新render
  2. 这里每次重新得到actualList之后浏览器要移除并添加节点在dom上,最优的情况应该是重用这些节点,让第一个和最后一个节点来回移动
  3. check all怎么跟下面的子checkbox同步起来,每个group的checkbox怎么跟其他checkbox同步起来
  4. 下拉框的按钮怎么显示用户选好的项目
  5. 怎么放弃选择,点ok保存
  6. 怎么区分用户正在点下拉框外面(关下拉框)还是里面正常操作
  7. 怎么实时监听输入的list的变化,用户的选择变化

下篇再说

---------------------------------其他-----------------------------

背景:

目前市面上的很多下拉框(包括bootstrap的各种angular/react实现,kendoUI,antd)都还没有考虑大数据量的情况。这样的话一旦数据突破4000条(单选)/1000(多选),浏览器就会变卡,因为DOM tree已经绘制了所有这些nodes。

吐槽:

这种情况很少见,但不是没有,比如我们的场景是允许用户创建自己的条目,一旦共享之后这条项目就会出现在某个多选框里让其他人自由组合使用,数据量就很容易变大。

发现:

Angular更新7的时候我注意到他们的material design库新增加了一个新玩意:虚拟滚动。本质上就是只绘制用户看到的节点去极大地节省内存开销。blog.angular.io/version-7-o…

过了不久后看见了阿健大叔在前端之巅发表的《如何用react+rxjs实现一个虚拟滚动组件》受启发,就决定用这种思想重写我们正在用的angular多选下拉框。


转载于:https://juejin.im/post/5c8944a46fb9a049a42fe913

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值