用粘性部分实现三节标题

时间:2019-07-18 09:23:14

标签: html css angular flexbox rxjs

我必须实现一个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';
  }
}

0 个答案:

没有答案