UNPKG

@jood/appearer

Version:
318 lines (310 loc) 8.42 kB
import 'intersection-observer'; import { Subject } from 'rxjs'; /** * 화면(지정된 root 영역)에 진입 여부를 판단하고 알리기 위한 클래스. * 등록된 Actor(s)를 IntersectionObserver 를 통해 관찰하고 관찰된 상태에 따라 Actor 에게 알림. * @template T Actor */ class AppearStage { /** * 초기화 * @param [option] 초기 옵션. 인터섹션 옵저버는 생성시에만 옵션 지정이 가능 */ init(option = {}) { if (!this.observer) { this.actorMap = new Map(); this.observer = new IntersectionObserver(this.onObserveEntries.bind(this), option); } } /** * 전달된 actor 를 옵저버에 등록합니다. * @param actor 등록할 Actor */ observe(actor) { const { element } = actor; if (this.actorMap && !this.actorMap.has(element)) { actor.bind(this); this.actorMap.set(element, actor); this.intersectionObserver.observe(element); } } /** * 전달된 actor 를 옵저버에서 제외합니다. * @param actor 제외할 Actor */ unobserve(actor) { const { element } = actor; if (this.actorMap && this.actorMap.has(element)) { this.actorMap.delete(element); this.intersectionObserver.unobserve(element); } } /** * 옵저버에 등록(관찰) 중 인 Actor 의 수 */ get actorSize() { return this.actorMap ? this.actorMap.size : 0; } /** * 등록 되어있는 Actor 를 반환 합니다. * @returns T[] */ getActors() { return Array.from(this.actorMap ? this.actorMap.values() : []); } /** * 생성된 intersection observer 인스턴스 */ get intersectionObserver() { if (!this.observer) throw new Error("uninitialize"); return this.observer; } /** * 옵저버의 콜백 핸들러 * @param entries 옵저버의 콜백으로 전달받는 엔트리 값 */ onObserveEntries(entries) { entries.forEach((entry) => { if (!this.actorMap) return; const { isIntersecting, target } = entry; const actor = this.actorMap.get(target); if (actor) { if (isIntersecting) { actor.appear(entry); } else { actor.disappear(entry); } } }); } /** * 파기 */ dispose() { if (this.observer) { this.observer.disconnect(); this.observer = null; } if (this.actorMap) { this.actorMap.clear(); this.actorMap = null; } } } /** * Actor type 구분 * @export * @enum {number} */ var AppearType; (function (AppearType) { AppearType["BASE"] = "base"; AppearType["ONCE"] = "once"; AppearType["LAZY"] = "lazy"; })(AppearType || (AppearType = {})); /** * 관찰대상의 이벤트 * @implements {AppearEventData<T>} * @template T */ class AppearEvent { /** * @param type 이벤트 타입 * @param option 이벤트 데이터 */ constructor(type, option) { const { actor, entry } = option; this.type = type; this.actor = actor; this.entry = entry; } } /** * 이벤트 타입 - 진입 */ AppearEvent.APPEAR = "APPEAR"; /** * 이벤트 타입 - 이탈 */ AppearEvent.DISAPPEAR = "DISAPPEAR"; /** * Stage 에 등록될 Actor. * 스테이지에 진입, 이탈 시 계속 알려주는 기본형. * @class BaseActor * @implements {IActor} */ class BaseActor { /** * @param element 옵저버에 등록되어야 하는 native element */ constructor(element) { /** * 이벤트 Observable * @see https://rxjs-dev.firebaseapp.com/guide/subject */ this.events = new Subject(); /** * 현재 진입 여부 상태 */ this.isAppear = null; this.element = element; } /** * 해당 인스턴스를 관찰하는 스테이지를 연결 * @param stage 스테이지 */ bind(stage) { this.stage = stage; } /** * 진입, 이탈 등 이벤트 알림 * @param type 이벤트 타입 * @param [entry] 상태 변경시 관찰된 상태 */ dispatch(type, entry) { this.events.next(new AppearEvent(type, { actor: this, entry, })); } /** * 스테이지 진입 * @param [entry] 스테이지 진입시 관찰 상태 */ appear(entry) { if (this.isAppear === true) return; this.isAppear = true; this.dispatch(AppearEvent.APPEAR, entry); } /** * 스테이지 이탈 * @param [entry] 스테이지 이탈시 관찰 상태 */ disappear(entry) { if (this.isAppear === false) return; this.isAppear = false; this.dispatch(AppearEvent.DISAPPEAR, entry); } /** * 파기 */ dispose() { if (this.stage) { this.stage.unobserve(this); } } } /** * Stage 에 등록될 Actor. * 스테이지 진입을 한번만 감지한 후 본인 스스로 관찰 해제하는 감지형. * (사용 예: 화면 진입시 한번만 애니메이션 한다, 이미지 로드를 한다) * @class OnceActor * @extends {BaseActor} */ class OnceActor extends BaseActor { /** * 스테이지 진입. 진입시 자동 관찰 해제. * @override * @param [entry] */ appear(entry) { if (this.isAppear === true) return; this.isAppear = true; this.dispatch(AppearEvent.APPEAR, entry); if (this.stage) { this.stage.unobserve(this); } } } /** * Stage 에 등록될 Actor. * 스테이지에 진입을 한번만 감지하되, 진입 후 너무 빠르게 이탈시에는 감지 처리를 하지 않는 느린 감지형. * (사용 예: 촘촘한 상품 목록과 같이 빠르게 스크롤 하여 지나칠 수 있는 곳) * @class LazyActor * @extends {BaseActor} */ class LazyActor extends BaseActor { constructor() { super(...arguments); this.appearTimer = null; this.checkoutDelay = 1000; this.appearDelay = 150; } /** * 느린 감지를 시작하기 전 대기 시간. * 지정된 시간 전에 감지된 진입은 느린 감지를 하지 않고 바로 진입을 알림. * @param [delay=1000] */ setCheckoutDelay(delay = 1000) { this.checkoutDelay = delay; } /** * 지정된 시간 사이에 진입 후 진출을 하는 경우 진입 알림을 하지 않는 대기 시간. * @param [delay=150] */ setAppearDelay(delay = 150) { this.appearDelay = delay; } /** * 진입 대기 타이머 파기 */ clearAppearTimer() { if (this.appearTimer) { clearTimeout(this.appearTimer); this.appearTimer = null; } } /** * 스테이지 진입. 진입 후 일정시간 (appearDelay) 전에 이탈하는 경우는 진입으로 취급하지 않음. * @override * @param [entry] */ appear(entry) { this.clearAppearTimer(); if (this.isAppear === true) return; if (this.checkoutDelay <= entry.time) { this.appearTimer = setTimeout(() => { this.doAppear(entry); }, this.appearDelay); } else { this.doAppear(entry); } } /** * 실제 진입 처리. * @private * @param entry */ doAppear(entry) { this.isAppear = true; this.dispatch(AppearEvent.APPEAR, entry); if (this.stage) { this.stage.unobserve(this); } } /** * 스테이지 이탈. * @override * @param entry */ disappear(entry) { this.clearAppearTimer(); if (!this.isAppear) return; this.isAppear = false; this.dispatch(AppearEvent.DISAPPEAR, entry); } } /** * Generated bundle index. Do not edit. */ export { AppearEvent, AppearStage, AppearType, BaseActor, LazyActor, OnceActor }; //# sourceMappingURL=jood-appearer.js.map