Ng-zorro中实现两个table同步滚动效果

某天下午,同事向我请教了一个问题,需求是有两个table,当table存在滚动条时,在某一个table上滚动,那么另一个table也要同步滚动,巧的是我也不会,但是我闲着没事,帮着研究了一下。

技术栈是: angular8 + ng-zorro组件库

先看效果和代码:

html代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<div class="syn-margin-left-24" style="display: flex;">
<div>
<nz-table [nzData]="leftData" [nzScroll]="{y: '300px'}" style="width: 95%;" [nzShowPagination]=false>
<thead>
<tr>
<th >Name</th>
<th >Age</th>
<th >Address</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of leftData">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
<td>{{data.address}}</td>
</tr>
</tbody>
</nz-table>
</div>
<div>
<nz-table [nzData]="rightData" [nzScroll]="{y: '300px'}" style="width: 95%;" [nzShowPagination]=false>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Address</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of rightData">
<td>{{data.name}}</td>
<td>{{data.age}}</td>
<td>{{data.address}}</td>
</tr>
</tbody>
</nz-table>
</div>
</div>

ts代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import { StorageUtil } from './../shared/utils/storage-util';
import { AfterViewInit, Component } from '@angular/core';
class Person {
name?: string;
age?: number;
address?: string;
}
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
})
export class DashboardComponent implements AfterViewInit {
leftData: Person[] = this.generateItems('Edward King',32,'London, Park Lane no.',25);
rightData: Person[] = this.generateItems('John Brown',42,'New York No. 1 Lake Park',15);
ngAfterViewInit(): void {
const element = document.querySelectorAll('.ant-table-body');
const leftTable = element[0];
const rightTable = element[1];
const scale = (leftTable.scrollHeight - leftTable.clientHeight) / (rightTable.scrollHeight - rightTable.clientHeight);
if (leftTable?.scrollHeight && leftTable.clientHeight && leftTable?.scrollHeight && leftTable.clientHeight) {
let flag = true;
leftTable.addEventListener('mouseover',() => {
flag = false;
leftTable.addEventListener('scroll',() => {
if (!flag) {
rightTable.scrollTop = leftTable.scrollTop / scale;
rightTable.scrollLeft = leftTable.scrollLeft;
}
})
});
rightTable.addEventListener('mouseover',() => {
flag = true;
rightTable.addEventListener('scroll',() => {
if (flag) {
leftTable.scrollTop = rightTable.scrollTop * scale;
leftTable.scrollLeft = rightTable.scrollLeft;
}
})
});
}
}
generateItems(name: string,age: number,address: string,size: number): Person[] {
const res: Person[] = [];
for (let i = 0; i < size; i++) {
res.push(
{
name: `${name}${i}`,
age: age + i,
address: `${address}${i}`
}
)
}
return res;
}
}

看ts代码的第23行,querySelectAll()方法可以获取所有class名为"ant-table-body"的元素,这个class是ng-zorro框架内部的类,可以通过浏览器的审查元素看到:

可能你会有疑问为什么我会找这个class呢?这是因为我们要监听存在滚动条的容器的scroll事件,所以自然的要找到这个容器。

现在已经把Dom找出来了,下一步就是设置让两个table的scrollTop和scrollLeft的值相同即可。

有两个需要注意的地方:

  1. 上面的代码我们设置了个flag值,是为了防止左右两个table相互赋值导致滑动缓慢,同时也可减少浏览器的性能消耗,你可以去掉flag字段看看,滑动会变卡。

  2. scale值是为了防止两个table的高度不一样设置的,这样的话滑动的时候两个table会同时到达底部或顶部。如果两个table的高度相同那scale就没啥意义了,加不加都一样。

完整的思路如下:

  1. 找到关键点,使两个table同步滚动的关键就是找到并设置两个table滚动区域的scrollTop、scrollLeft的值相同即可。

  2. 因为用的是ng-zorro框架,所以通过审查元素定位到某个div。

  3. 通过querySelectorAll()方法获取两个table的dom对象,获取dom对象的方式有很多种,在这里只能用querySelectorAll()方法。

  4. 添加监听事件,先监听mouseover再监听scroll事件。

  5. 设置两个容器的scrollTop和scrollLeft的值相同。

  6. 考虑反思有没有问题,比如两个table高度不一样等,然后再想怎么完善。

解决该问题用到了很多基础的js的知识,比如原生的鼠标监听事件,mouseover、scroll等,还有scrollTop、scrollHeigh、clientHeight等属性的理解,由此看出有一个好的基础还是挺重要的!

以上。笔者水平有限,若有错误敬请指正,不明白的地方也可评论区留言交流~