當 better-scroll 碰見 angular

今天在掘金上看到黃老師的文章當 better-scroll 碰見 Vue,因而乎,便用angular嘗試封裝了一下,感受還不賴!特此博文分享。css

better-scroll 是什麼

better-scroll 是一款重點解決移動端(已支持 PC)各類滾動場景需求的插件。它的核心是借鑑的 iscroll 的實現,它的 API 設計基本兼容 iscroll,在 iscroll 的基礎上又擴展了一些 feature 以及作了一些性能優化。 better-scroll 是基於原生 JS 實現的,不依賴任何框架。它編譯後的代碼大小是 63kb,壓縮後是 35kb,gzip 後僅有 9kb,是一款很是輕量的 JS lib。html

github 源碼

安裝

yarn add iwe7-ng-better-scroll
複製代碼

api

使用

import {
  NgBetterScrollModule,
  BetterScrollConfigDefault
} from "iwe7-ng-better-scroll";

@NgModule({
  imports: [
    ...
    NgBetterScrollModule.forRoot(BetterScrollConfigDefault)
    ...
  ]
})
export class AppModule {}
複製代碼

在模板中使用

<better-scroll (onPullingDown)="init($event)"
  (onPullingUp)="loadMore($event)">
  <ul>
    <li *ngFor="let item of list">
      {{item}}
    </li>
  </ul>
</better-scroll>
複製代碼
import {
  Component,
  OnInit,
  ElementRef,
  InjectionToken,
  Inject,
  AfterViewInit,
  ViewChild,
  Input,
  OnChanges,
  SimpleChanges,
  EventEmitter,
  Output,
  OnDestroy
} from "@angular/core";
// import BScroll from "better-scroll";
// 爲了兼容才這樣寫
import * as _better_scroll from "better-scroll";
const BScroll = (_better_scroll as any).default || _better_scroll;

export const BETTER_SCROLL_CONFIG = new InjectionToken("BETTER_SCROLL_CONFIG");
import { CdkObserveContent } from "@angular/cdk/observers";

export const BetterScrollConfigDefault = {
  startX: 0,
  startY: 0,
  scrollX: false,
  scrollY: true,
  freeScroll: false,
  directionLockThreshold: 5,
  eventPassthrough: "",
  click: false,
  tap: false,
  bounce: true,
  bounceTime: 800,
  momentum: true,
  momentumLimitTime: 300,
  momentumLimitDistance: 15,
  swipeTime: 2500,
  swipeBounceTime: 500,
  deceleration: 0.001,
  flickLimitTime: 200,
  flickLimitDistance: 100,
  resizePolling: 60,
  probeType: 0,
  preventDefault: true,
  preventDefaultException: {
    tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/
  },
  HWCompositing: true,
  useTransition: true,
  useTransform: true,
  bindToWrapper: false,
  observeDOM: true,
  autoBlur: true,
  wheel: false,
  snap: false,
  scrollbar: false,
  pullDownRefresh: {
    threshold: 50,
    stop: 20
  },
  pullUpLoad: { threshold: 50 },
  mouseWheel: {
    speed: 20,
    invert: false
  },
  stopPropagation: false
};

export interface BetterScrollWhellInterface {
  selectedIndex: number;
  rotate: number;
  adjustTime: number;
  wheelWrapperClass: string;
  wheelItemClass: string;
}

export interface BetterScrollEasingInterface {
  style: string;
  fn: Function;
}

export interface BetterScrollSnapInterface {
  loop: boolean;
  threshold: number;
  stepX: number;
  stepY: number;
  easing: BetterScrollEasingInterface;
}

export interface BetterScrollScrollbarInterface {
  fade: boolean;
  interactive: boolean;
}

export interface BetterScrollPullDownRefreshInterface {
  threshold: number;
  stop: number;
}

export interface BetterScrollPullUpLoadInterface {
  threshold: number;
}

export interface BetterScrollMouseWheelInterface {
  speed: number;
  invert: boolean;
}

@Component({
  selector: "better-scroll",
  templateUrl: "./better-scroll.component.html",
  styleUrls: ["./better-scroll.component.scss"]
})
export class BetterScrollComponent implements OnInit, OnChanges, OnDestroy {
  scroll: any;
  // 配置選項
  // 橫軸方向初始化位置。
  @Input() startX: number = 0;
  // 縱軸方向初始化位置
  @Input() startY: number = 0;
  // 當設置爲 true 的時候,能夠開啓橫向滾動
  @Input() scrollX: boolean = false;
  // 當設置爲 true 的時候,能夠開啓縱向滾動
  @Input() scrollY: boolean = true;
  // 有些場景咱們須要支持橫向和縱向同時滾動,而不只限制在某個方向,這個時候咱們只要設置 freeScroll 爲 true 便可
  @Input() freeScroll: boolean = false;
  // 當咱們須要鎖定只滾動一個方向的時候,咱們在初始滾動的時候根據橫軸和縱軸滾動的絕對值作差,當差值大於 directionLockThreshold 的時候來決定滾動鎖定的方向。
  @Input() directionLockThreshold: number = 5;
  // 有時候咱們使用 better-scroll 在某個方向模擬滾動的時候,但願在另外一個方向保留原生的滾動(好比輪播圖,咱們但願橫向模擬橫向滾動,而縱向的滾動仍是保留原生滾動,咱們能夠設置 eventPassthrough 爲 vertical;相應的,若是咱們但願保留橫向的原生滾動,能夠設置eventPassthrough爲 horizontal)
  @Input() eventPassthrough: string = "";
  // better-scroll 默認會阻止瀏覽器的原生 click 事件。當設置爲 true,better-scroll 會派發一個 click 事件,咱們會給派發的 event 參數加一個私有屬性 _constructed,值爲 true。
  @Input() click: boolean = false;
  // 由於 better-scroll 會阻止原生的 click 事件,咱們能夠設置 tap 爲 true,它會在區域被點擊的時候派發一個 tap 事件,你能夠像監聽原生事件那樣去監聽它,如 element.addEventListener('tap', doSomething, false);。若是 tap 設置爲字符串, 那麼這個字符串就做爲自定義事件名稱。如 tap: 'myCustomTapEvent'。
  @Input() tap: boolean = false;
  // 當滾動超過邊緣的時候會有一小段回彈動畫。設置爲 true 則開啓動畫。
  @Input() bounce: boolean = true;
  // 設置回彈動畫的動畫時長。
  @Input() bounceTime: number = 800;
  // 當快速在屏幕上滑動一段距離的時候,會根據滑動的距離和時間計算出動量,並生成滾動動畫。設置爲 true 則開啓動畫。
  @Input() momentum: boolean = true;
  // 只有在屏幕上快速滑動的時間小於 momentumLimitTime,才能開啓 momentum 動畫
  @Input() momentumLimitTime: number = 300;
  // 只有在屏幕上快速滑動的距離大於 momentumLimitDistance,才能開啓 momentum 動畫。
  @Input() momentumLimitDistance: number = 15;
  // 設置 momentum 動畫的動畫時長。
  @Input() swipeTime: number = 2500;
  // 設置當運行 momentum 動畫時,超過邊緣後的回彈整個動畫時間
  @Input() swipeBounceTime: number = 500;
  // 表示 momentum 動畫的減速度。
  @Input() deceleration: number = 0.001;
  // 有的時候咱們要捕獲用戶的輕拂動做(短期滑動一個較短的距離)。只有用戶在屏幕上滑動的時間小於 flickLimitTime ,纔算一次輕拂。
  @Input() flickLimitTime: number = 200;
  // 只有用戶在屏幕上滑動的距離小於 flickLimitDistance ,纔算一次輕拂。
  @Input() flickLimitDistance: number = 100;
  // 當窗口的尺寸改變的時候,須要對 better-scroll 作從新計算,爲了優化性能,咱們對從新計算作了延時。60ms 是一個比較合理的值。
  @Input() resizePolling: number = 60;
  // 有時候咱們須要知道滾動的位置。當 probeType 爲 1 的時候,會非實時(屏幕滑動超過必定時間後)派發scroll 事件;當 probeType 爲 2 的時候,會在屏幕滑動的過程當中實時的派發 scroll 事件;當 probeType 爲 3 的時候,不只在屏幕滑動的過程當中,並且在 momentum 滾動動畫運行過程當中實時派發 scroll 事件。若是沒有設置該值,其默認值爲 0,即不派發 scroll 事件。
  @Input() probeType: number = 0;
  // 當事件派發後是否阻止瀏覽器默認行爲。這個值應該設爲 true,除非你真的知道你在作什麼,一般你可能用到的是 preventDefaultException。
  @Input() preventDefault: boolean = true;
  // better-scroll 的實現會阻止原生的滾動,這樣也同時阻止了一些原生組件的默認行爲。這個時候咱們不能對這些元素作 preventDefault,因此咱們能夠配置 preventDefaultException。默認值 {tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/}表示標籤名爲 input、textarea、button、select 這些元素的默認行爲都不會被阻止。
  @Input()
  preventDefaultException: any = {
    tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/
  };
  // 是否開啓硬件加速,開啓它會在 scroller 上添加 translateZ(0) 來開啓硬件加速從而提高動畫性能,有很好的滾動效果。
  @Input() HWCompositing: boolean = true;
  // 是否使用 CSS3 transition 動畫。若是設置爲 false,則使用 requestAnimationFrame 作動畫。
  @Input() useTransition: boolean = true;
  // 是否使用 CSS3 transform 作位移。若是設置爲 false, 則設置元素的 top/left (這種狀況須要 scroller 是絕對定位的)。
  @Input() useTransform: boolean = true;
  // move 事件一般會綁定到 document 上而不是滾動的容器上,當移動的過程當中光標或手指離開滾動的容器滾動仍然會繼續,這一般是指望的。固然你也能夠把 move 事件綁定到滾動的容器上,bindToWrapper 設置爲 true 便可,這樣一旦移動的過程當中光標或手指離開滾動的容器,滾動會馬上中止。
  @Input() bindToWrapper: boolean = false;
  // 當在移動端環境(支持 touch 事件),disableMouse 會計算爲 true,這樣就不會監聽鼠標相關的事件,而在 PC 環境,disableMouse 會計算爲 false,就會監聽鼠標相關事件,不建議修改該屬性,除非你知道你在作什麼。
  @Input() disableMouse: boolean;
  // 當在移動端環境(支持 touch 事件),disableTouch 會計算爲 false,這樣會監聽 touch 相關的事件,而在 PC 環境,disableTouch 會計算爲 true,就不會監聽 touch 相關事件。不建議修改該屬性,除非你知道你在作什麼。
  @Input() disableTouch: boolean;
  // 會檢測 scroller 內部 DOM 變化,自動調用 refresh 方法從新計算來保證滾動的正確性。它會額外增長一些性能開銷,若是你能明確地知道 scroller 內部 DOM 的變化時機並手動調用 refresh 從新計算,你能夠把該選項設置爲 false。
  @Input() observeDOM: boolean = false;
  // 在滾動以前會讓當前激活的元素(input、textarea)自動失去焦點
  @Input() autoBlur: boolean = true;
  // 是否阻止事件冒泡。
  @Input() stopPropagation: boolean = false;

  // ----高級 ----- //
  @Input() wheel: BetterScrollWhellInterface | boolean = false;
  @Input() snap: BetterScrollSnapInterface | boolean = false;
  @Input() scrollbar: BetterScrollScrollbarInterface | boolean = false;
  @Input()
  pullDownRefresh: BetterScrollPullDownRefreshInterface | boolean = {
    threshold: 50,
    stop: 20
  };
  @Input()
  pullUpLoad: BetterScrollPullUpLoadInterface | boolean = { threshold: 50 };
  @Input()
  mouseWheel: BetterScrollMouseWheelInterface | boolean = {
    speed: 20,
    invert: false
  };

  // outputs
  @Output() onBeforeScrollStart: EventEmitter<this> = new EventEmitter();
  @Output() onScroll: EventEmitter<BScroll.Position> = new EventEmitter();

  // 組件內部
  // 是否已經建立了
  isCreated: boolean = false;

  @Output() onScrollCancel: EventEmitter<any> = new EventEmitter();
  @Output() onScrollEnd: EventEmitter<BScroll.Position> = new EventEmitter();
  @Output() onTouchEnd: EventEmitter<BScroll.Position> = new EventEmitter();
  @Output() onFlick: EventEmitter<any> = new EventEmitter();
  @Output() onRefresh: EventEmitter<any> = new EventEmitter();
  @Output() onDestroy: EventEmitter<any> = new EventEmitter();

  @Output() onPullingDown: EventEmitter<this> = new EventEmitter();
  @Output() onPullingUp: EventEmitter<this> = new EventEmitter();

  constructor(
    // 經過注入的ElementRef訪問dom
    public ele: ElementRef,
    // 經過注入 配置咱們的默認參數
    @Inject(BETTER_SCROLL_CONFIG) public _default: any
  ) {}
  /**
   * 若是配置改變而且已經建立了 那麼銷燬從新建立
   */
  ngOnChanges(changes: SimpleChanges) {
    let hasChanged: boolean = false;
    for (let key in changes) {
      this._default[key] = changes[key].currentValue;
      hasChanged = true;
    }
    if (hasChanged && this.isCreated) {
      this.destroy();
      this.createScroll();
    }
  }
  /**
   * 註銷移除
   */
  ngOnDestroy() {
    this.destroy();
  }

  ngOnInit() {}

  ngAfterViewInit() {
    this.createScroll();
  }

  /**
   * 監控dom內容變動
   * 刷新better scroll
   */
  changeContent(e: any) {
    this.refresh();
  }
  /**
   * 初始化better scroll
   */
  createScroll() {
    this.scroll = new BScroll(this.ele.nativeElement, this._default);
    // 已經建立了
    this.isCreated = true;
    this.scroll.on("beforeScrollStart", () => {
      this.onBeforeScrollStart.emit(this);
    });
    this.scroll.on("scroll", (pos: BScroll.Position) => {
      this.onScroll.emit(pos);
    });
    this.scroll.on("scrollCancel", () => {
      this.onScrollCancel.emit();
    });
    this.scroll.on("scrollEnd", (pos: BScroll.Position) => {
      this.onScrollEnd.emit(pos);
    });
    this.scroll.on("touchEnd", (pos: BScroll.Position) => {
      this.onTouchEnd.emit(pos);
    });
    this.scroll.on("flick", () => {
      this.onFlick.emit();
    });
    this.scroll.on("refresh", () => {
      this.onRefresh.emit();
    });
    this.scroll.on("destroy", () => {
      this.onDestroy.emit();
    });
    this.scroll.on("pullingDown", () => {
      this.onPullingDown.emit(this);
    });
    this.scroll.on("pullingUp", () => {
      this.onPullingUp.emit(this);
    });
  }

  // 刷新狀態
  refresh(): this {
    this.scroll.refresh();
    return this;
  }
  // 啓用 better-scroll, 默認 開啓
  enable(): this {
    this.scroll.enable();
    return this;
  }
  // 禁用 better-scroll,DOM 事件(如 touchstart、touchmove、touchend)的回調函數再也不響應
  disable(): this {
    this.scroll.disable();
    return this;
  }
  // 相對於當前位置偏移滾動 x,y 的距離
  scrollBy(x: number, y: number, time?: number, easing?: object): this {
    this.scroll.scrollBy(x, y, time, easing);
    return this;
  }
  // 滾動到指定的位置
  scrollTo(x: number, y: number, time?: number, easing?: object): this {
    this.scroll.scrollTo(x, y, time, easing);
    return this;
  }
  // 滾動到指定的目標元素
  scrollToElement(
    el: HTMLElement | string,
    time?: number,
    offsetX?: number | boolean,
    offsetY?: number | boolean,
    easing?: object
  ): this {
    this.scroll.scrollToElement(el, time, offsetX, offsetY, easing);
    return this;
  }
  // 當即中止當前運行的滾動動畫
  stop(): this {
    this.scroll.stop();
    return this;
  }
  // 銷燬 better-scroll,解綁事件
  destroy(): this {
    this.scroll.destroy();
    return this;
  }

  // 當咱們作 slide 組件的時候,slide 一般會分紅多個頁面。調用此方法能夠滾動到指定的頁面。
  goToPage(x: number, y: number, time?: number, easing?: object): this {
    this.scroll.goToPage(x, y, time, easing);
    return this;
  }
  // 滾動到下一個頁面
  next(time?: number, easing?: object): this {
    this.next(time, easing);
    return this;
  }
  // 滾動到上一個頁面
  prev(time?: number, easing?: object): this {
    this.scroll.prev(time, easing);
    return this;
  }
  // 獲取當前頁面的信息
  getCurrentPage(): {
    x: number;
    y: number;
    pageX: number;
    pageY: number;
  } {
    return this.scroll.getCurrentPage();
  }
  // 當咱們作 picker 組件的時候,調用該方法能夠滾動到索引對應的位置
  wheelTo(index: number): this {
    this.scroll.wheelTo(index);
    return this;
  }
  // 獲取當前選中的索引值
  getSelectedIndex(): number {
    return this.scroll.getSelectedIndex();
  }
  // 當下拉刷新數據加載完畢後,須要調用此方法告訴 better-scroll 數據已加載
  finishPullDown(): this {
    this.scroll.finishPullDown();
    return this;
  }
  // 當上拉加載數據加載完畢後,須要調用此方法告訴 better-scroll 數據已加載
  finishPullUp(): this {
    this.scroll.finishPullUp();
    return this;
  }
  trigger(type: string): this {
    this.scroll.trigger();
    return this;
  }
}
複製代碼
相關文章
相關標籤/搜索