From e813db2fae26447f3f772b28b4c8e5272130f39d Mon Sep 17 00:00:00 2001 From: juanT Date: Sat, 3 Oct 2020 02:16:38 +0200 Subject: [PATCH 1/6] feat(scrollspy): add basic scrollspy --- src/app/app.component.ts | 7 ++++- src/app/shared/shared.module.ts | 2 +- .../views/home/content/content.component.css | 27 ++++++++++++++++ .../views/home/content/content.component.html | 12 ++++++- .../views/home/content/content.component.ts | 31 +++++++++++++++++-- 5 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index ac156319..b471cbcd 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, HostListener } from '@angular/core'; import { SidenavService } from '@shared/services/sidenav.service'; import { MatSidenav } from '@angular/material/sidenav'; @Component({ @@ -14,4 +14,9 @@ export class AppComponent { ngAfterViewInit(): void { this.sidenavService.set(this.sidenav); } + + @HostListener('window:scroll', ['$event']) + onScroll(event: any) { + console.log('SCROL APP'); + } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 9c8910ce..07a367f6 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -49,7 +49,7 @@ const MATERIAL_MODULES = [ ]; @NgModule({ - declarations: COMPONENTS, + declarations: [COMPONENTS], imports: [MATERIAL_MODULES, CORE_MODULES, FlexLayoutModule], exports: [COMPONENTS, MATERIAL_MODULES, CORE_MODULES, FlexLayoutModule], }) diff --git a/src/app/views/home/content/content.component.css b/src/app/views/home/content/content.component.css index e69de29b..6044bf32 100644 --- a/src/app/views/home/content/content.component.css +++ b/src/app/views/home/content/content.component.css @@ -0,0 +1,27 @@ +.md-container { + display: flex; +} + +.md-container > * { + margin: 0 30px; + min-width: 0; /* Fix flex horizontal overflow */ +} + +.scrollspy-container { + padding: 4px; + display: inline-table; + position: sticky; + top: 20px; + border-left: 1px solid gray; + display: inline-table; +} + +.scrollspy-container > div { + cursor: pointer; + margin-bottom: 0.6em; + transition: all 0.3s; +} + +.scrollspy-container > div :hover { + color: rgb(148, 7, 78); +} diff --git a/src/app/views/home/content/content.component.html b/src/app/views/home/content/content.component.html index d85837d4..afe610ce 100644 --- a/src/app/views/home/content/content.component.html +++ b/src/app/views/home/content/content.component.html @@ -1,3 +1,13 @@
- +
+ +
+
diff --git a/src/app/views/home/content/content.component.ts b/src/app/views/home/content/content.component.ts index 82b0ba64..6a9dee51 100644 --- a/src/app/views/home/content/content.component.ts +++ b/src/app/views/home/content/content.component.ts @@ -1,4 +1,10 @@ -import { Component, AfterViewChecked } from '@angular/core'; +import { + Component, + AfterViewInit, + HostListener, + AfterContentInit, + AfterContentChecked, +} from '@angular/core'; import { HighlightService } from 'src/app/shared/services/highlight.service'; @Component({ @@ -6,10 +12,29 @@ import { HighlightService } from 'src/app/shared/services/highlight.service'; templateUrl: './content.component.html', styleUrls: ['./content.component.css'], }) -export class ContentComponent implements AfterViewChecked { +export class ContentComponent implements AfterContentChecked { + currentSection = ''; + sections = []; + constructor(private highlightService: HighlightService) {} - ngAfterViewChecked(): void { + ngAfterContentChecked(): void { this.highlightService.highlightAll(); + this.aa(); + } + + scrollTo(sectionId) { + document.querySelector('#' + sectionId).scrollIntoView(); + } + + private aa() { + const elements = document.querySelectorAll('h2'); + this.sections = []; + [].forEach.call(elements, (element) => { + this.sections.push({ + name: element.textContent, + id: element.id, + }); + }); } } From 7735a28dbaad13ba973560ff1c4df758a8689b2e Mon Sep 17 00:00:00 2001 From: juanT Date: Sat, 3 Oct 2020 02:30:49 +0200 Subject: [PATCH 2/6] refactor(sidenav): rename some names --- src/app/views/home/content/content.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/views/home/content/content.component.ts b/src/app/views/home/content/content.component.ts index 6a9dee51..90193a8e 100644 --- a/src/app/views/home/content/content.component.ts +++ b/src/app/views/home/content/content.component.ts @@ -20,20 +20,20 @@ export class ContentComponent implements AfterContentChecked { ngAfterContentChecked(): void { this.highlightService.highlightAll(); - this.aa(); + this.loadSections(); } scrollTo(sectionId) { document.querySelector('#' + sectionId).scrollIntoView(); } - private aa() { - const elements = document.querySelectorAll('h2'); + private loadSections() { + const sectionsDivs = document.querySelectorAll('h2'); this.sections = []; - [].forEach.call(elements, (element) => { + [].forEach.call(sectionsDivs, (div: HTMLDivElement) => { this.sections.push({ - name: element.textContent, - id: element.id, + name: div.textContent, + id: div.id, }); }); } From 7d6d40fe855ea694132c3f42f22787118af29cc9 Mon Sep 17 00:00:00 2001 From: juanT Date: Sat, 3 Oct 2020 02:50:58 +0200 Subject: [PATCH 3/6] refactor(sidenav): move sidenav stuff to component --- .../scroll-spy/scroll-spy.component.css | 19 +++++++++++ .../scroll-spy/scroll-spy.component.html | 8 +++++ .../scroll-spy/scroll-spy.component.spec.ts | 25 +++++++++++++++ .../scroll-spy/scroll-spy.component.ts | 32 +++++++++++++++++++ src/app/shared/shared.module.ts | 10 ++++-- .../views/home/content/content.component.css | 21 +----------- .../views/home/content/content.component.html | 7 +--- .../views/home/content/content.component.ts | 27 +--------------- 8 files changed, 95 insertions(+), 54 deletions(-) create mode 100644 src/app/shared/components/scroll-spy/scroll-spy.component.css create mode 100644 src/app/shared/components/scroll-spy/scroll-spy.component.html create mode 100644 src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts create mode 100644 src/app/shared/components/scroll-spy/scroll-spy.component.ts diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.css b/src/app/shared/components/scroll-spy/scroll-spy.component.css new file mode 100644 index 00000000..a0912b18 --- /dev/null +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.css @@ -0,0 +1,19 @@ +.root { + padding: 4px; + padding-left: 8px; + display: inline-table; + position: sticky; + top: 20px; + border-left: 1px solid gray; + display: inline-table; +} + +.root > div { + cursor: pointer; + margin-bottom: 0.6em; + transition: all 0.3s; +} + +.root > div :hover { + color: rgb(148, 7, 78); +} diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.html b/src/app/shared/components/scroll-spy/scroll-spy.component.html new file mode 100644 index 00000000..b2d49a0b --- /dev/null +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.html @@ -0,0 +1,8 @@ + diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts b/src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts new file mode 100644 index 00000000..a99f11b7 --- /dev/null +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ScrollSpyComponent } from './scroll-spy.component'; + +describe('ScrollSpyComponent', () => { + let component: ScrollSpyComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ScrollSpyComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ScrollSpyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.ts b/src/app/shared/components/scroll-spy/scroll-spy.component.ts new file mode 100644 index 00000000..da952248 --- /dev/null +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.ts @@ -0,0 +1,32 @@ +import { Component, AfterContentChecked } from '@angular/core'; + +@Component({ + selector: 'rxjs-scroll-spy', + templateUrl: './scroll-spy.component.html', + styleUrls: ['./scroll-spy.component.css'], +}) +export class ScrollSpyComponent implements AfterContentChecked { + currentSection = ''; + sections = []; + + constructor() {} + + scrollTo(sectionId) { + document.querySelector('#' + sectionId).scrollIntoView(); + } + + ngAfterContentChecked(): void { + this.loadSections(); + } + + private loadSections() { + const sectionsDivs = document.querySelectorAll('h2'); + this.sections = []; + [].forEach.call(sectionsDivs, (div: HTMLDivElement) => { + this.sections.push({ + name: div.textContent, + id: div.id, + }); + }); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 07a367f6..fcac3058 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -23,10 +23,16 @@ import { NgModule } from '@angular/core'; import { MatDividerModule } from '@angular/material/divider'; import { SidenavComponent } from './components/sidenav/sidenav.component'; import { RouterModule } from '@angular/router'; +import { ScrollSpyComponent } from './components/scroll-spy/scroll-spy.component'; const CORE_MODULES = [CommonModule, FormsModule, ReactiveFormsModule]; -const COMPONENTS = [FooterComponent, HeaderComponent, SidenavComponent]; +const COMPONENTS = [ + FooterComponent, + HeaderComponent, + SidenavComponent, + ScrollSpyComponent, +]; const MATERIAL_MODULES = [ MatInputModule, @@ -49,7 +55,7 @@ const MATERIAL_MODULES = [ ]; @NgModule({ - declarations: [COMPONENTS], + declarations: [COMPONENTS, ScrollSpyComponent], imports: [MATERIAL_MODULES, CORE_MODULES, FlexLayoutModule], exports: [COMPONENTS, MATERIAL_MODULES, CORE_MODULES, FlexLayoutModule], }) diff --git a/src/app/views/home/content/content.component.css b/src/app/views/home/content/content.component.css index 6044bf32..fb15683b 100644 --- a/src/app/views/home/content/content.component.css +++ b/src/app/views/home/content/content.component.css @@ -3,25 +3,6 @@ } .md-container > * { - margin: 0 30px; + margin: 0 24px; min-width: 0; /* Fix flex horizontal overflow */ } - -.scrollspy-container { - padding: 4px; - display: inline-table; - position: sticky; - top: 20px; - border-left: 1px solid gray; - display: inline-table; -} - -.scrollspy-container > div { - cursor: pointer; - margin-bottom: 0.6em; - transition: all 0.3s; -} - -.scrollspy-container > div :hover { - color: rgb(148, 7, 78); -} diff --git a/src/app/views/home/content/content.component.html b/src/app/views/home/content/content.component.html index afe610ce..89942c31 100644 --- a/src/app/views/home/content/content.component.html +++ b/src/app/views/home/content/content.component.html @@ -3,11 +3,6 @@ diff --git a/src/app/views/home/content/content.component.ts b/src/app/views/home/content/content.component.ts index 90193a8e..7da811cf 100644 --- a/src/app/views/home/content/content.component.ts +++ b/src/app/views/home/content/content.component.ts @@ -1,10 +1,4 @@ -import { - Component, - AfterViewInit, - HostListener, - AfterContentInit, - AfterContentChecked, -} from '@angular/core'; +import { Component, AfterContentChecked } from '@angular/core'; import { HighlightService } from 'src/app/shared/services/highlight.service'; @Component({ @@ -13,28 +7,9 @@ import { HighlightService } from 'src/app/shared/services/highlight.service'; styleUrls: ['./content.component.css'], }) export class ContentComponent implements AfterContentChecked { - currentSection = ''; - sections = []; - constructor(private highlightService: HighlightService) {} ngAfterContentChecked(): void { this.highlightService.highlightAll(); - this.loadSections(); - } - - scrollTo(sectionId) { - document.querySelector('#' + sectionId).scrollIntoView(); - } - - private loadSections() { - const sectionsDivs = document.querySelectorAll('h2'); - this.sections = []; - [].forEach.call(sectionsDivs, (div: HTMLDivElement) => { - this.sections.push({ - name: div.textContent, - id: div.id, - }); - }); } } From 4382af5c7a7a924c548c75c2dfd68ca5d6ca91f2 Mon Sep 17 00:00:00 2001 From: juanT Date: Sat, 3 Oct 2020 14:22:46 +0200 Subject: [PATCH 4/6] feat(scrollspy): show actual section Using cdkScrollable --- src/app/app.component.html | 2 +- src/app/app.component.ts | 5 -- src/app/app.module.ts | 2 + .../scroll-spy/scroll-spy.component.css | 27 ++++++- .../scroll-spy/scroll-spy.component.html | 17 +++-- .../scroll-spy/scroll-spy.component.ts | 74 ++++++++++++++++--- 6 files changed, 103 insertions(+), 24 deletions(-) diff --git a/src/app/app.component.html b/src/app/app.component.html index dda4a231..2195e272 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -4,7 +4,7 @@ - + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index b471cbcd..24a03d76 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -14,9 +14,4 @@ export class AppComponent { ngAfterViewInit(): void { this.sidenavService.set(this.sidenav); } - - @HostListener('window:scroll', ['$event']) - onScroll(event: any) { - console.log('SCROL APP'); - } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0ce5615f..78b67cfe 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,6 +9,7 @@ import { MarkdownModule } from 'ngx-markdown'; import { SharedModule } from './shared/shared.module'; import { HttpClientModule, HttpClient } from '@angular/common/http'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { ScrollingModule } from '@angular/cdk/scrolling'; @NgModule({ declarations: [AppComponent], @@ -21,6 +22,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; SharedModule, MarkdownModule.forRoot({ loader: HttpClient }), FontAwesomeModule, + ScrollingModule, ], providers: [], bootstrap: [AppComponent], diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.css b/src/app/shared/components/scroll-spy/scroll-spy.component.css index a0912b18..99eca712 100644 --- a/src/app/shared/components/scroll-spy/scroll-spy.component.css +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.css @@ -8,12 +8,33 @@ display: inline-table; } -.root > div { - cursor: pointer; +.section { + display: inline-flex; + align-items: center; margin-bottom: 0.6em; + cursor: pointer; transition: all 0.3s; } -.root > div :hover { +.actual-section-indicator { + width: 0; + height: 10px; + margin-left: -17px; + margin-right: 15px; + border-radius: 100px; + background-color: white; + border: 1px solid white; + flex: none; + display: inline-block; + transition: all 0.3s; +} + +.section:hover { color: rgb(148, 7, 78); } + +.actual-section-indicator.active { + width: 16px; + margin-right: 5px; + border: 1px solid black; +} diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.html b/src/app/shared/components/scroll-spy/scroll-spy.component.html index b2d49a0b..4078c7bb 100644 --- a/src/app/shared/components/scroll-spy/scroll-spy.component.html +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.html @@ -1,8 +1,13 @@ diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.ts b/src/app/shared/components/scroll-spy/scroll-spy.component.ts index da952248..2cd5db07 100644 --- a/src/app/shared/components/scroll-spy/scroll-spy.component.ts +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.ts @@ -1,32 +1,88 @@ -import { Component, AfterContentChecked } from '@angular/core'; +import { + Component, + AfterContentChecked, + ChangeDetectorRef, + OnInit, +} from '@angular/core'; +import { debounce, throttle, debounceTime } from 'rxjs/operators'; +import { ScrollDispatcher, CdkScrollable } from '@angular/cdk/overlay'; +import { interval } from 'rxjs'; @Component({ selector: 'rxjs-scroll-spy', templateUrl: './scroll-spy.component.html', styleUrls: ['./scroll-spy.component.css'], }) -export class ScrollSpyComponent implements AfterContentChecked { +export class ScrollSpyComponent implements AfterContentChecked, OnInit { currentSection = ''; sections = []; + private sectionsHeader: NodeListOf; - constructor() {} + constructor( + private scroll: ScrollDispatcher, + private changeDetector: ChangeDetectorRef + ) {} - scrollTo(sectionId) { - document.querySelector('#' + sectionId).scrollIntoView(); + ngOnInit() { + this.scroll + .scrolled() + .pipe(throttle((ev) => interval(100))) + .subscribe((scroll: CdkScrollable) => this.onScroll(scroll)); } ngAfterContentChecked(): void { this.loadSections(); } + scrollTo(sectionId) { + document.querySelector('#' + sectionId).scrollIntoView(); + } + + isActualSection(sectionId: string) { + return sectionId === this.currentSection; + } + private loadSections() { - const sectionsDivs = document.querySelectorAll('h2'); + this.sectionsHeader = document.querySelectorAll('h2'); this.sections = []; - [].forEach.call(sectionsDivs, (div: HTMLDivElement) => { + this.sectionsHeader.forEach((header: HTMLHeadingElement) => { this.sections.push({ - name: div.textContent, - id: div.id, + name: header.textContent, + id: header.id, }); }); } + + private onScroll(scroll: CdkScrollable) { + const scrollTop = scroll.getElementRef().nativeElement.scrollTop || 0; + const parentOffset = scroll.getElementRef().nativeElement.offsetTop; + const currentSection = this.getCurrentSection({ scrollTop, parentOffset }); + + if (!currentSection || currentSection.id === this.currentSection) { + return; + } + + this.currentSection = currentSection.id; + this.changeDetector.detectChanges(); + } + + private getCurrentSection({ scrollTop, parentOffset }) { + const sectionsCount = this.sectionsHeader.length; + let actualHeader; + for (let i = 0; i < sectionsCount; i++) { + const header = this.sectionsHeader[i]; + if (header.offsetTop - parentOffset <= scrollTop) { + actualHeader = header; + } + } + + return actualHeader; + + /* For some reason, this (and similar) DONT work. Only get first element + return [].find.call( + this.sectionsHeader, + (header: HTMLHeadingElement) => + header.offsetTop - parentOffset <= scrollTop + ); */ + } } From 9067f1d51fc9c36a77293f702d176db89997e4f7 Mon Sep 17 00:00:00 2001 From: Juan Torres Date: Thu, 8 Oct 2020 11:53:03 +0200 Subject: [PATCH 5/6] Update scroll-spy.component.css --- src/app/shared/components/scroll-spy/scroll-spy.component.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/components/scroll-spy/scroll-spy.component.css b/src/app/shared/components/scroll-spy/scroll-spy.component.css index 99eca712..39257af9 100644 --- a/src/app/shared/components/scroll-spy/scroll-spy.component.css +++ b/src/app/shared/components/scroll-spy/scroll-spy.component.css @@ -9,7 +9,7 @@ } .section { - display: inline-flex; + display: flex; align-items: center; margin-bottom: 0.6em; cursor: pointer; From c21eaefa45ea6329b0fd57af2593e096ad65fc82 Mon Sep 17 00:00:00 2001 From: Juan Torres Date: Thu, 8 Oct 2020 13:35:41 +0200 Subject: [PATCH 6/6] style(scrollspy): hide in narrow screens and limit width --- src/app/views/home/content/content.component.css | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/app/views/home/content/content.component.css b/src/app/views/home/content/content.component.css index 58a7c0d8..c91e4da1 100644 --- a/src/app/views/home/content/content.component.css +++ b/src/app/views/home/content/content.component.css @@ -74,6 +74,10 @@ th { padding: 25px; } +.scrollspy-container { + max-width: 450px; +} + .page-heading { display: flex; justify-content: space-between; @@ -123,3 +127,10 @@ summary:hover { padding: 40px; } } + +@media only screen and (max-width: 700px) { + .scrollspy-container { + display: none; + } +} +