今天在掘金上看到黃老師的文章當 better-scroll 碰見 Vue,因而乎,便用angular嘗試封裝了一下,感受還不賴!特此博文分享。css
better-scroll 是一款重點解決移動端(已支持 PC)各類滾動場景需求的插件。它的核心是借鑑的 iscroll 的實現,它的 API 設計基本兼容 iscroll,在 iscroll 的基礎上又擴展了一些 feature 以及作了一些性能優化。 better-scroll 是基於原生 JS 實現的,不依賴任何框架。它編譯後的代碼大小是 63kb,壓縮後是 35kb,gzip 後僅有 9kb,是一款很是輕量的 JS lib。html
yarn add iwe7-ng-better-scroll
複製代碼
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;
}
}
複製代碼