import { EventEmitter } from '../event';
import { IVisibilityStatus } from './types';
const doc = window.document as any;
/**
* 실행된 browser 기준으로, visibilityChange event 에 맞는 vendorPrefix 반환
* @memberof VisibilityChangeObserver
* @export
* @returns {String}
*/
export function getVendorPrefix(): string {
let prefix = '';
if (typeof doc.hidden !== 'undefined') {
prefix = '';
} /* istanbul ignore next */ else if (typeof doc.msHidden !== 'undefined') {
prefix = 'ms';
} /* istanbul ignore next */ else if (typeof doc.webkitHidden !== 'undefined') {
prefix = 'webkit';
}
return prefix;
}
/**
* getVendorPrefix 의 값
* @constant
* @memberof VisibilityChangeObserver
* @type {String}
*/
export const VENDOR_PREFIX = getVendorPrefix();
/**
* browser 상의 visibilitychange event 명
* @constant
* @memberof VisibilityChangeObserver
* @type {String}
*/
export const VISIBILITY_EVENT_NAME = `${VENDOR_PREFIX}visibilitychange`;
/**
* browser 상의 visibility hidden 시점의 event 명
* @constant
* @memberof VisibilityChangeObserver
* @type {String}
*/
export const HIDDEN_METHOD_NAME = VENDOR_PREFIX ? /* istanbul ignore next */ `${VENDOR_PREFIX}Hidden` : 'hidden';
/**
* VisibilityChange Event Types
* @event VisibilityChangeObserver#VISIBILITY_EVENTS
* @memberof VisibilityChangeObserver
* @property {String} CHANGE - 변경 시점
* @property {String} GET_STATUS - 현재 상태값 체크
*/
// for jsdoc
/**
* @export
* @readonly
* @enum {VISIBILITY_EVENTS}
*/
export enum VISIBILITY_EVENTS {
CHANGE = 'VISIBILITY_CHANGE-EVENTS-CHANGE',
GET_STATUS = 'VISIBILITY_CHANGE-EVENTS-GET_STATUS'
}
/**
* browser 의 visibility 변화를 감지
<iframe
src="https://codesandbox.io/embed/nonollcode-snippet-9gko8?autoresize=1&expanddevtools=1&fontsize=14&hidenavigation=1&initialpath=%2Fobserver-VisibilityChange.html&module=%2Fobserver-VisibilityChange.html&theme=dark"
style="width:100%; height:500px; border:1px solid black; border-radius: 4px; overflow:hidden;"
title="@nonoll/code-snippet"
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
></iframe>
* @export
* @class VisibilityChangeObserver
* @alias observer/VisibilityChangeObserver
* @extends {EventEmitter}
* @see https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
* @see https://caniuse.com/#search=visibilityState
* @throws {Error} browser 에서 지원하지 못하는 경우 - throw new Error('VisibilityChange 지원되지 않는 브라우저입니다.');
* @example
import { VisibilityChangeObserver, VISIBILITY_EVENTS } from '@nonoll/code-snippet/observer';
const createElement = ({ tag = 'div', id = '', style = '', value = '', text = '' }) => {
const doc = window.document;
const target = doc.createElement(tag);
target.setAttribute('id', id);
target.setAttribute('style', style);
target.setAttribute('value', value);
if (text) {
target.textContent = text;
}
return target;
}
let observer;
const forExample = () => {
if (observer) {
console.log('already example');
return;
}
const doc = window.document;
observer = new VisibilityChangeObserver();
observer.on(VISIBILITY_EVENTS.CHANGE, ({ isHidden, isShow }) => {
console.log('change', isHidden, isShow);
});
observer.attach();
observer.emit(VISIBILITY_EVENTS.GET_STATUS);
const statusButton = createElement({ tag: 'button', text: 'status' });
const openButton = createElement({ tag: 'button', text: 'window open' });
doc.body.appendChild(statusButton);
doc.body.appendChild(openButton);
statusButton.addEventListener('click', e => {
e.preventDefault();
console.log('statusButton clicked');
if (!observer) {
return;
}
// observer.emit(VISIBILITY_EVENTS.GET_STATUS);
const { isHidden, isShow } = observer.getStatus();
console.log('getStatus', isHidden, isShow);
});
openButton.addEventListener('click', e => {
e.preventDefault();
console.log('openButton clicked');
if (!observer) {
return;
}
const browser = window.open('https://nonoll.github.io/code-snippet/');
if (!browser) {
console.error('팝업이 차단되어 있습니다.');
}
});
}
forExample();
*/
export class VisibilityChangeObserver extends EventEmitter {
constructor() {
super();
/* istanbul ignore next: for not support error */
if (!VisibilityChangeObserver.isSupport()) {
throw new Error('VisibilityChange 지원되지 않는 브라우저입니다.');
}
this.onVisibilityChangeListener = this.onVisibilityChangeListener.bind(this);
}
/**
* visibilitychange event 명 반환
* @static
* @returns {String}
* @memberof VisibilityChangeObserver
*/
static visibilityEventName(): string {
return `${VENDOR_PREFIX}visibilitychange`;
}
/**
* visibility hidden event 명 반환
* @static
* @returns {String}
* @memberof VisibilityChangeObserver
*/
static hiddenMethodName(): string {
return HIDDEN_METHOD_NAME;
}
/**
* visibility change 지원 여부 반환
* @static
* @returns {boolean}
* @memberof VisibilityChangeObserver
*/
static isSupport(): boolean {
return typeof doc[VisibilityChangeObserver.hiddenMethodName()] !== 'undefined';
}
/**
* @private
* @memberof VisibilityChangeObserver
* @see IVisibilityStatus
* @fires VisibilityChangeObserver#VISIBILITY_EVENTS
*/
private onVisibilityChangeListener(): void {
const isHidden = this.isHidden();
this.emit(VISIBILITY_EVENTS.CHANGE, { isHidden, isShow: !isHidden });
}
/**
* @private
* @returns {Boolean}
* @memberof VisibilityChangeObserver
*/
private isHidden(): boolean {
return doc[VisibilityChangeObserver.hiddenMethodName()];
}
/**
* 현재 상태를 반환
* @returns {IVisibilityStatus}
* @memberof VisibilityChangeObserver
*/
public getStatus(): IVisibilityStatus {
this.emit(VISIBILITY_EVENTS.GET_STATUS);
const isHidden = this.isHidden();
return { isHidden, isShow: !isHidden };
}
/**
* 이벤트 감지 설정
* @returns {VisibilityChangeObserver}
* @memberof VisibilityChangeObserver
*/
public attach(): VisibilityChangeObserver {
doc.addEventListener(VisibilityChangeObserver.visibilityEventName(), this.onVisibilityChangeListener, false);
this.on(VISIBILITY_EVENTS.GET_STATUS, this.onVisibilityChangeListener);
return this;
}
/**
* 이벤트 감지 해제
* @returns {VisibilityChangeObserver}
* @memberof VisibilityChangeObserver
*/
public detach(): VisibilityChangeObserver {
doc.removeEventListener(VisibilityChangeObserver.visibilityEventName(), this.onVisibilityChangeListener, false);
this.off(VISIBILITY_EVENTS.GET_STATUS, this.onVisibilityChangeListener);
return this;
}
/**
* destory
* @returns {VisibilityChangeObserver}
* @memberof VisibilityChangeObserver
*/
public destroy(): VisibilityChangeObserver {
this.detach();
return this;
}
}