import { Injectable, OnDestroy, NgZone } from '@angular/core'; import { fromEvent, Subject, Observable, Subscription } from 'rxjs'; import { throttleTime, debounceTime } from 'rxjs/operators'; import { animationFrame } from 'rxjs/internal/scheduler/animationFrame'; interface ScrollElement { element: HTMLElement, id: string } interface ScrollSpy { elements: ScrollElement[]; currentSectionId: string; subject: Subject; clickChange?: boolean //判断是否是点击导致的页面滚动 } interface ScrollSpies { [scrollSpyId: string]: ScrollSpy } interface ScrollFollow { element: HTMLElement, id: string } interface ScrollFollowSpipes { [scrollFollowId: string]: ScrollFollow[] } interface ScrollContents { [scrollFollowId: string]: HTMLElement } interface Offsets { [scrollFollowId: string]: number } interface ScrollChange { [scrollFollowId: string]: Subject } interface CurrentId { subject: Subject; idValue: string } interface CurrentIdObj { [groupId: string]: CurrentId } @Injectable({ providedIn: 'root' }) export class FarrisScrollSpyService implements OnDestroy { // 监听区块当前时候的样式 private scrollSpyPartActiveClsName = 'f-scrollspy-part-active'; private fixedTabContainerClsName = 'f-scrollspy-container-fixed'; private parentFixedTabContainerClsName='f-scrollspy-container-fixed-parent'; private scrollSubscription: Subscription; /**被监听滚动的contents */ scrollContents: ScrollContents = {}; /**被监听滚动的sections*/ scrollSpies: ScrollSpies = {}; /**监听滚动的锚点部分 */ scrollFollowSpipes: ScrollFollowSpipes = {}; private scrollSpyListFollowTypeChanges = {}; /**滚动监听偏移距离 */ offsets: Offsets = {}; /**默认监听分组名称 */ defaultId: string = 'default'; // currentSectionId: Subject = new Subject(); currentSectionIdObj: CurrentIdObj = {}; //获取currentid getCurrentSectionId(groupId: string) { if (!groupId) { groupId = this.defaultId; } return this.currentSectionIdObj[groupId].subject; // return this.currentSectionId; } //滚动变化时改变currentid setCurrentSectionId(groupId: string, value: string) { if (!groupId) { groupId = this.defaultId; } this.currentSectionIdObj[groupId].subject.next(value); //console.log(this.currentSectionIdObj); // this.currentSectionId.next(value); } //设置不同group组的currentid setCurrentSectionIdObj(groupId: string, value: string) { if (!groupId) { groupId = this.defaultId; } if (!this.currentSectionIdObj[groupId]) { this.currentSectionIdObj[groupId] = { subject: new Subject(), idValue: value } as CurrentId; } else { this.currentSectionIdObj[groupId].idValue = value; } setTimeout(() => { if(this.scrollFollowSpipes[groupId] && this.scrollFollowSpipes[groupId].length){ let item = this.scrollFollowSpipes[groupId].find(scrollFollowItem=>{ return scrollFollowItem['id'] == this.currentSectionIdObj[groupId].idValue; }) if(item){ this.fScrollFollowClick(groupId,item); } } }, 0); // this.fScrollFollowClick(key,follow); } scrollChange: ScrollChange = {}; constructor(public ngzone: NgZone) { } public ngOnDestroy(): void { this.scrollSubscription.unsubscribe(); } /**Observer部分 供指令初始化调用 */ // public getCurrentSection$(scrollSpyId: string = this.defaultId): Observable { // if(scrollSpyId !== ''){ // return this.scrollChange[scrollSpyId]; // } // else{ // return this.scrollChange[this.defaultId]; // } // } public updateScrollGroup(scrollSpyId: string = this.defaultId) { const groupId = scrollSpyId !== '' ? scrollSpyId : this.defaultId; if (!this.scrollChange[groupId]) { this.scrollChange[groupId] = new Subject(); } return this.scrollChange[groupId]; } /**content部分滚动事件监听 */ private subscribeScroll(key: string): void { this.ngzone.runOutsideAngular(() => { this.scrollSubscription = fromEvent(this.scrollContents[key], 'scroll') .pipe(debounceTime(20, animationFrame)) .subscribe((): void => { if (this.scrollSpies[key]) { const { currentSectionId, elements, subject } = this.scrollSpies[key]; const scrollTop = this.scrollContents[key].scrollTop; const topElementInView = elements.filter((el) => { return el.element.offsetTop <= scrollTop + this.offsets[key] } ); if (!topElementInView || !topElementInView.length) { return; } //排序按照设置Spy的顺序,假定顺序是一致的 {id} let qlSpyItem=this.scrollFollowSpipes[key].find(spyItem=>{ let eqElItem=topElementInView.find(elItem=>{ return elItem.id==spyItem.id; }) return eqElItem?true:false; }); //console.log(topElementInView); const topElementId: string = qlSpyItem?qlSpyItem.id:topElementInView[0].id; //判断 点击是否是点击引起的滚动 if (this.scrollSpies[key].clickChange) { this.scrollSpies[key].clickChange = false; return; } else { this.scrollSpies[key].clickChange = false; if (topElementId !== currentSectionId) { this.scrollSpies[key].currentSectionId = topElementId; this.ngzone.run(() => { subject.next(topElementId); }) } } } }) }); } /**tab部分点击事件监听 */ private subscribeClick(key): void { //Object.keys(this.scrollFollowSpipes).forEach((key:string):void=>{ if (this.scrollFollowSpipes[key] && this.scrollFollowSpipes[key].length) { this.scrollFollowSpipes[key].forEach((follow) => { fromEvent(follow.element, 'click') .pipe(throttleTime(100)) .subscribe((event) => { event.stopPropagation(); this.fScrollFollowClick(key, follow); // if(this.scrollSpies[key]){ // const { currentSectionId, elements, subject } = this.scrollSpies[key]; // this.scrollSpies[key].clickChange = true; // if(follow.id !== currentSectionId){ // const topElementInView = elements.find(el => el.id === follow.id); // if(!topElementInView) { return; } // this.scrollSpies[key].currentSectionId = follow.id; // //this.scrollContents[key].offsetTop // this.scrollContents[key].scrollTop = Math.round(topElementInView.element.offsetTop - this.offsets[key]); // subject.next(follow.id); // } // } }) }) } //}); } fScrollFollowClick(key, follow){ if(this.scrollSpies[key]){ const { currentSectionId, elements, subject } = this.scrollSpies[key]; this.scrollSpies[key].clickChange = true; if (follow.id !== currentSectionId) { const topElementInView = elements.find(el => el.id === follow.id); if (!topElementInView) { return; } this.scrollSpies[key].currentSectionId = follow.id; //this.scrollContents[key].offsetTop if (this.scrollContents[key]) { // 内容去有滚动 this.scrollContents[key].scrollTop = Math.round(topElementInView.element.offsetTop - this.offsets[key]- parseInt(getComputedStyle(this.scrollContents[key]).paddingTop,10)); } subject.next(follow.id); } } } /**添加需要被滚动监听部分 */ public addContentElement(element: HTMLElement, contentId = this.defaultId): void { if (contentId !== '') { this.scrollContents[contentId] = element; this.subscribeScroll(contentId); } else { this.scrollContents[this.defaultId] = element; this.subscribeScroll(this.defaultId); } // 判断是有待改变的状态 this.updateContainerClsName(contentId); } /**移除需要被滚动监听部分元素 */ public removeContentElement(contentId = this.defaultId): void { const removeId = contentId !== '' ? contentId : this.defaultId; delete this.scrollSpies[removeId]; } /**添加滚动监听tab部分元素*/ public addFollowElement(element: HTMLElement, followId, followContentId = this.defaultId): void { const groupId = followContentId !== '' ? followContentId : this.defaultId; const follow = { element: element, id: followId }; if (!this.scrollFollowSpipes[groupId]) { this.scrollFollowSpipes[groupId] = [] } //-------------------------此处没有处理重复ID const follows = this.scrollFollowSpipes[groupId]; follows.push(follow); this.scrollFollowSpipes[groupId] = follows; this.subscribeClick(groupId); } /**移除滚动监听tab部分元素 */ public removeFollowElement(followId, followContentId = this.defaultId): void { const elements = this.scrollFollowSpipes[followContentId].filter(el => { return el.id !== followId; }) if (!elements.length) { delete this.scrollFollowSpipes[followContentId]; return; } this.scrollFollowSpipes[followContentId] = elements; } /**添加滚动监听锚点部分元素 */ public addElement(element: HTMLElement, scrollId: string, scrollGroupId: string = this.defaultId): void { if (!this.scrollSpies[scrollGroupId]) { this.scrollSpies[scrollGroupId] = { elements: [], currentSectionId: '', subject: new Subject(), } as ScrollSpy; this.scrollSpies[scrollGroupId].subject .subscribe((currentSection: string): void => { if (this.scrollChange[scrollGroupId]) { this.scrollChange[scrollGroupId].next(currentSection); } }); } if (this.hasElement(scrollId, scrollGroupId)) { return; } const el: ScrollElement = { element: element, id: scrollId } // 加入时,判断是否是当前 this.updateScrollSpyActiveClsName(scrollGroupId,el); const elements: any[] = this.scrollSpies[scrollGroupId].elements; elements.push(el); elements.sort((a, b): number => b.element.getBoundingClientRect().top - a.element.getBoundingClientRect().top); this.scrollSpies[scrollGroupId].elements = elements; } /**移除滚动监听锚点部分元素 */ public removeElement(scrollId: string, scrollGroupId: string = this.defaultId): void { const elements = this.scrollSpies[scrollGroupId].elements.filter((el): boolean => { return el.id !== scrollId } ); if (!elements.length) { delete this.scrollSpies[scrollGroupId]; return; } this.scrollSpies[scrollGroupId].elements = elements; } /**判断scrollGroupId 是否有id名为elementId 的元素 */ private hasElement(elementId: string, scrollGroupId: string): boolean { return this.scrollSpies[scrollGroupId].elements.some((element): boolean => element.id === elementId); } /**设置offset */ public setOffset(offset: number = 0, contentId: string = this.defaultId): void { if (contentId !== '') { this.offsets[contentId] = offset; } else { this.offsets[this.defaultId] = offset; } } /*ScrollSpy组件(锚点列表区域)初始化预设defaultId */ setGroupId(groupid) { if (groupid) { this.defaultId = groupid; } } getGroupId() { return this.defaultId; } /*改变 ScrollSpy组件(锚点列表区域)的显示状态时 */ changeGroupIdFollowType(groupid, followTypeData) { this.scrollSpyListFollowTypeChanges[groupid] = followTypeData; this.updateContainerClsName(groupid); } private updateContainerClsName(groupid) { // 没有待更新变化 if (!this.scrollSpyListFollowTypeChanges.hasOwnProperty(groupid)) { return; } // container并不存在的时候 if (!this.scrollContents.hasOwnProperty(groupid)) { return; } let followTypeData = this.scrollSpyListFollowTypeChanges[groupid]; let containerEl = this.scrollContents[groupid]; // Container的Element if (followTypeData['prev'] == 'fixedTab') { //移除class containerEl.className = containerEl.className.replace(this.fixedTabContainerClsName, ''); containerEl.parentElement.classList.remove(this.parentFixedTabContainerClsName); } else if (followTypeData['next'] == 'fixedTab') { // 增加class containerEl.className += ' ' + this.fixedTabContainerClsName; // 给父元素增加高度 containerEl.parentElement.classList.add(this.parentFixedTabContainerClsName); } this.scrollSpyListFollowTypeChanges[groupid] = null; } updateScrollSpiesActiveClsName(groupId,currentId) { // 追加、移除class this.scrollSpies[groupId].elements.map((item: ScrollElement) => { if (item.id == currentId) { // 找到当前 if (item.element.className.indexOf(this.scrollSpyPartActiveClsName) < 0) { item.element.className += ' ' + this.scrollSpyPartActiveClsName; } } else { // 其他 item.element.className = item.element.className.replace(this.scrollSpyPartActiveClsName, ''); } }); } /** * 当追加ScrollSpy块的时候,判断是否是已经设置的当前 * @param groupId * @param scrollEl */ private updateScrollSpyActiveClsName(groupId,scrollEl:ScrollElement){ // 如果已设置 if(this.currentSectionIdObj[groupId]){ if(this.currentSectionIdObj[groupId]['idValue']==scrollEl.id){ scrollEl.element.className+=' '+this.scrollSpyPartActiveClsName; } } } }