如何在Angular 4+中实现PDF文档对比的滚动同步功能
Angular 4+ 实现文档对比滚动同步功能
要实现类似Draftable的滚动同步(滚动一侧文档,另一侧自动定位到对应内容,即使两份文档存在差异),核心是先建立两份文档内容的位置映射关系,再基于这个映射处理滚动同步逻辑,具体步骤如下:
一、生成内容位置映射表
这是实现精准同步的关键——你需要先对比两份文档的内容,找出对应内容块在左右文档中的滚动位置对应关系。
- 如果是纯文本/HTML文档:可以用
diff-match-patch这类差异对比库,找出两份文档中匹配的段落/文本块,记录每个块在左侧文档的top位置(距离容器顶部的距离)和右侧文档对应的top位置,形成一个映射数组,比如:interface ScrollMapItem { leftTop: number; rightTop: number; leftHeight: number; rightHeight: number; } const scrollMap: ScrollMapItem[] = [ { leftTop: 0, rightTop: 0, leftHeight: 100, rightHeight: 120 }, // 更多匹配块的映射... ]; - 如果是PDF文档:先用
pdfjs-dist解析PDF,提取每页的文本和布局信息(每个文本块的位置、高度),再对比生成上述映射表。
二、Angular组件中实现滚动同步
1. 模板结构
给左右两个文档容器添加标识,方便获取DOM元素:
<div class="document-container" #leftDoc> <!-- 左侧文档内容 --> </div> <div class="document-container" #rightDoc> <!-- 右侧文档内容 --> </div>
2. 组件逻辑
在组件中获取容器元素,监听滚动事件,基于映射表计算同步位置:
import { Component, ViewChild, ElementRef, OnInit } from '@angular/core'; import { debounceTime, fromEvent } from 'rxjs'; @Component({ selector: 'app-document-compare', templateUrl: './document-compare.component.html', styleUrls: ['./document-compare.component.css'] }) export class DocumentCompareComponent implements OnInit { @ViewChild('leftDoc') leftDoc!: ElementRef<HTMLDivElement>; @ViewChild('rightDoc') rightDoc!: ElementRef<HTMLDivElement>; // 预先生成的滚动映射表(实际项目中从差异对比逻辑获取) private scrollMap: ScrollMapItem[] = []; // 用于防止滚动事件循环触发的标记 private isSyncing = false; ngOnInit(): void { this.initScrollListeners(); // 这里调用你的差异对比逻辑生成scrollMap this.generateScrollMap(); } private initScrollListeners(): void { // 左侧滚动监听 fromEvent(this.leftDoc.nativeElement, 'scroll') .pipe(debounceTime(10)) // 防抖优化,减少计算频率 .subscribe(() => { if (!this.isSyncing) { this.syncScroll('left'); } }); // 右侧滚动监听 fromEvent(this.rightDoc.nativeElement, 'scroll') .pipe(debounceTime(10)) .subscribe(() => { if (!this.isSyncing) { this.syncScroll('right'); } }); } private syncScroll(source: 'left' | 'right'): void { this.isSyncing = true; const sourceEl = source === 'left' ? this.leftDoc.nativeElement : this.rightDoc.nativeElement; const targetEl = source === 'left' ? this.rightDoc.nativeElement : this.leftDoc.nativeElement; const scrollTop = sourceEl.scrollTop; // 找到当前滚动位置对应的映射项 const matchedItem = this.scrollMap.find(item => { if (source === 'left') { return scrollTop >= item.leftTop && scrollTop <= item.leftTop + item.leftHeight; } else { return scrollTop >= item.rightTop && scrollTop <= item.rightTop + item.rightHeight; } }); if (matchedItem) { // 设置目标容器的滚动位置 targetEl.scrollTop = source === 'left' ? matchedItem.rightTop : matchedItem.leftTop; } else { // 没有匹配项时,按比例滚动(作为 fallback) const sourceRatio = scrollTop / (sourceEl.scrollHeight - sourceEl.clientHeight); targetEl.scrollTop = sourceRatio * (targetEl.scrollHeight - targetEl.clientHeight); } // 重置标记,避免阻止后续滚动 setTimeout(() => { this.isSyncing = false; }, 50); } private generateScrollMap(): void { // 这里替换成你的实际差异对比逻辑,生成scrollMap // 示例:假设左右文档有3个匹配块 this.scrollMap = [ { leftTop: 0, rightTop: 0, leftHeight: 150, rightHeight: 160 }, { leftTop: 180, rightTop: 200, leftHeight: 200, rightHeight: 190 }, { leftTop: 420, rightTop: 410, leftHeight: 120, rightHeight: 130 } ]; } } interface ScrollMapItem { leftTop: number; rightTop: number; leftHeight: number; rightHeight: number; }
三、优化要点
- 防抖处理:用
debounceTime减少滚动事件的计算次数,提升性能; - 避免循环触发:通过
isSyncing标记,防止同步滚动时触发对方的滚动事件,形成循环; - 平滑滚动:可以给
scrollTop设置添加过渡动画,比如在CSS中给.document-container添加scroll-behavior: smooth;; - 动态更新映射表:如果文档内容动态变化,要重新生成映射表。
内容的提问来源于stack exchange,提问作者Santhosh
相关产品推荐
相关产品推荐

