我必须实现一个3部分的粘性标头。第一部分是标题部分,第二部分是英雄组件,3d部分是子标题。当用户开始滚动页面时,第二部分(英雄组件)消失在标题的后面。 3d部分将粘贴到页眉。如果用户继续滚动并滚动了页面的30%,则3d部分也会开始隐藏在标题后面。
我有一些问题: 1)如何更好地组织代码(将标头拆分为组件) 2)以及如何使用Angular 8实现所描述的逻辑?
也许有一些Angular 2+的例子
我实现了一个子标题,用于隐藏滚动中的英雄部分
HTML
<div class="subheader__wrapper">
<div class="subheader subheader__sticky"
[ngClass]="getSubheaderClassTop()"
>
<div class="row align-middle align-justify subheader-row-height u-grid-full-width">
<cc-breadcrumb class="columns"></cc-breadcrumb>
<a *ngIf="!noBackButton" (click)="onBack()"
id="form-dropdown-back-link"
class="columns small-1 mos-u-text-size--md mos-u-color-text--white"
>
<my-icon size="lg" icon="keyboard_arrow_left" theme="disabled-light" alt="Back"></my-icon>
<span class="anchor-icon-text-fix">{{ 'subheader.back' | translate }}</span>
</a>
</div>
</div>
</div>
<hero
*ngIf="isHeroVisible"
class="mos-c-position--relative"
[backgroundUrl]="heroImageURL"
[backgroundImage]="gradient"
blendMode="multiply"
backgroundPosition="top"
flexDirection="row"
minHeight="192">
<div *ngIf="!isBlendMode" class="subheader__gradient"></div>
<div class="subheader__content flex-child-grow">
<div class="row x-large-row c-position--relative">
<div class="column medium-8 small-12 u-color-text--white x-large-6">
<h2 class="o-subheader-2" data-id="subheader">
{{ heroSubtitle }} {{heroSubtitleDate}}
</h2>
<h2 class="mos-o-display-2" data-id="header">
{{ heroTitle }}
</h2>
<hr class="subheader__separator c-hero--separator">
</div>
</div>
</div>
</hero>
CSS
:host {
display: block;
}
.subheader {
width: 100%;
height: 52px;
background-color: color(sapphire, 500);
z-index: 100;
&-row-height {
height: 52px;
}
&__sticky {
position: fixed;
}
&__gradient {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image: linear-gradient(to right, rgba(0,0,0,0.8) 0%, rgba(255, 255, 255, 0.2) 100%);
}
&__content {
z-index: 1;
width: 100%;
justify-self: center;
.subheader__separator {
margin-right: auto;
}
}
}
.circle-step-position {
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
JS
import {
AfterViewInit,
Component,
EventEmitter,
Input,
NgZone,
Output,
ElementRef,
ChangeDetectorRef,
ChangeDetectionStrategy,
} from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { pathOr } from 'ramda';
import { AutoUnsubscribe } from '../shared/auto-unsubscribe.decorator';
import { WindowRef } from '../services/api/aot-ref/windowRef';
import { fromEvent } from 'rxjs';
import { takeUntil, distinctUntilChanged, map, throttleTime, startWith } from 'rxjs/operators';
@Component({
selector: 'cc-section-subheader',
templateUrl: './subheader.component.html',
styleUrls: ['./subheader.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
@AutoUnsubscribe
export class SubheaderComponent implements AfterViewInit {
componentDestroy;
@Input() subheader: string;
@Input() header: string;
@Input() country: string;
@Input() noBackButton: boolean;
@Input() heroTitle: string;
@Input() heroSubtitle: string;
@Input() heroSubtitleDate: string;
@Input() heroImageURL: string;
@Input() isHeroVisible = false;
@Input() isSummary = false;
@Input() isFileUpload = false;
@Input() isValidateData = false;
@Input() isJobMatch = false;
@Input() isAggregateValidation = false;
@Output() back = new EventEmitter<void>();
adjustSubheader = true;
lastScroll = 0;
lastHeight: string;
isExpanded = false;
isBlendMode = false;
constructor(
private ngZone: NgZone,
private window: WindowRef,
private subheaderElement: ElementRef,
private translate: TranslateService,
private cdr: ChangeDetectorRef,
) {
this.isBlendMode = !!('backgroundBlendMode' in document.body.style);
}
get areButtonsDisabled() {
return true;
}
get gradient(): string {
return this.isBlendMode
? 'linear-gradient(to right, black 0%, white 100%)'
: null;
}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
fromEvent(this.window.nativeWindow, 'scroll')
.pipe(
map(() => this.isHeaderVisible()),
distinctUntilChanged(),
takeUntil(this.componentDestroy())
)
.subscribe((isHeaderVisible: boolean) => {
this.ngZone.run(() => {
this.adjustSubheader = isHeaderVisible;
this.cdr.detectChanges();
});
});
});
this.ngZone.runOutsideAngular(() => {
fromEvent(this.window.nativeWindow, 'resize').pipe(
startWith(null),
throttleTime(200),
takeUntil(this.componentDestroy()),
)
.subscribe(() => this.updateWrappersHeight());
});
}
/**
* Since sub-header has position set as fixed it removes the element from the normal document
* flow. To properly align content under it we need to set sub-header's height equal to
* sub-header.
*/
updateWrappersHeight(): void {
const el = this.subheaderElement.nativeElement;
const headerHeight = this.window.nativeWindow.getComputedStyle(el.querySelector('.subheader')).height;
if (headerHeight !== this.lastHeight) {
this.lastHeight = headerHeight;
el.querySelector('.subheader__wrapper').style.height = this.lastHeight;
}
}
onBack() {
this.back.emit();
}
getHtmlElementOffset(element: HTMLElement, offset = 0): number {
if (!element.offsetParent) {
return offset;
}
return this.getHtmlElementOffset(
element.offsetParent as HTMLElement,
offset + pathOr(0, ['offsetParent', 'offsetTop'], element),
);
}
getElementOffset(element: ElementRef): number {
if (!element) {
return 0;
}
return this.getHtmlElementOffset(
element.nativeElement,
element.nativeElement.offsetTop,
);
}
isHeaderVisible() {
const scrollTop = this.window.nativeWindow.pageYOffset;
const down = scrollTop > this.lastScroll;
const top = this.getElementOffset(this.subheaderElement);
this.lastScroll = scrollTop;
return scrollTop <= top;
}
getSubheaderClassTop(): string {
return this.adjustSubheader
? ''
: 'subheader__sticky--fixed-to-top';
}
}