Created
July 1, 2019 12:10
-
-
Save chy4egg/f2d93b76c4e92302d8e50f488c5b88f2 to your computer and use it in GitHub Desktop.
A typescript page-overlay with scroll-blocking feature
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// detectPassiveEvents - для избежания ошибок и увеличения производительности в chromium-браузерах | |
// @ts-ignore | |
import detectPassiveEvents from 'detect-passive-events' | |
const passiveEvents = detectPassiveEvents.hasSupport | |
type TConstructorOptions = { | |
className?: string, | |
offsetTop?: number, | |
color?: string, | |
zIndex?: string, | |
noContentScroll?: boolean, | |
} | |
/** | |
* Подложка для модальных окон. | |
* Имеет возможность блокировать скролл и может работать в качестве блокировщика скролла, если сама подложка не нужна. | |
* В таком случае можно вызывать static методы disableScroll и enableScroll в самом классе | |
*/ | |
export default class PageOverlay { | |
parentSelector: string | |
parentNode: HTMLElement | null | |
targetNode: HTMLElement | null | |
className: string | |
offsetTop: number | |
color: string | |
zIndex: string | |
footer: Element | null | |
footerInitialIndex: string | |
noContentScroll: boolean | |
constructor(parentSelector: string, constructorOptions: TConstructorOptions) { | |
this.parentSelector = parentSelector | |
this.parentNode = document.querySelector(this.parentSelector) || null | |
this.targetNode = this.parentNode ? document.querySelector(this.parentSelector) : null | |
this.className = constructorOptions.className || 'page-overlay-default' | |
this.offsetTop = constructorOptions.offsetTop || 0 | |
this.zIndex = constructorOptions.zIndex || '999' | |
this.color = constructorOptions.color || 'rgba(0,0,0,0.5)' | |
this.noContentScroll = constructorOptions.noContentScroll || false | |
this.footer = document.getElementById('footer') | |
// @ts-ignore | |
this.footerInitialIndex = (this.footer) ? this.footer.style.zIndex : '1' | |
this.init() | |
} | |
private createOverlay() { | |
const targetNode = document.createElement('div') | |
if (!this.parentNode || !targetNode) return | |
targetNode.classList.add(this.className) | |
targetNode.style.position = 'fixed' | |
targetNode.style.backgroundColor = this.color | |
targetNode.style.left = '0' | |
targetNode.style.right = '0' | |
targetNode.style.top = `${this.offsetTop}px` | |
targetNode.style.bottom = '0' | |
targetNode.style.zIndex = this.zIndex | |
targetNode.style.transition = 'opacity 0.2s ease' | |
targetNode.style.opacity = '0' | |
targetNode.style.visibility = 'hidden' | |
this.targetNode = targetNode | |
this.parentNode.appendChild(targetNode) | |
} | |
/** | |
* Проверяет, есть ли в родителе хоть одна подложка | |
*/ | |
private checkIfOverlayExists() { | |
if (!this.parentNode) return false | |
const oldOverlays = this.parentNode.querySelectorAll(`.${this.className}`) | |
return (Boolean(oldOverlays && oldOverlays.length)) | |
} | |
/** | |
* Удаляет старые подложки из родительского элемента | |
*/ | |
private destroyOldOverlays() { | |
if (!this.parentNode) return | |
const oldOverlays = this.parentNode.querySelectorAll(`.${this.className}`) | |
if (oldOverlays && oldOverlays.length) { | |
[].forEach.call(oldOverlays, (overlay: HTMLElement) => { | |
if (this.parentNode) this.parentNode.removeChild(overlay) | |
}) | |
} | |
} | |
/** | |
* Скрывает z-index у footer чтобы он не вылезал за overlay | |
*/ | |
private hideFooter() { | |
// @ts-ignore | |
if (this.footer) this.footer.style.zIndex = '0' | |
} | |
/** | |
* Возвращает z-index у footer к исходному состоянию | |
*/ | |
private showFooter() { | |
// @ts-ignore | |
if (this.footer) this.footer.style.zIndex = this.footerInitialIndex | |
} | |
static preventDefault(e: any) { | |
e = e || window.event | |
if (e.preventDefault) e.preventDefault() | |
e.returnValue = false | |
} | |
static keydown(e: any) { | |
// space: 32, pageup: 33, pagedown: 34, end: 35, home: 36 | |
// left: 37, up: 38, right: 39, down: 40, | |
const keys = [32, 33, 34, 35, 36, 37, 38, 39, 40] | |
keys.forEach((key: number) => { | |
if (e.keyCode === key) { | |
PageOverlay.preventDefault(e) | |
} | |
}) | |
} | |
static wheel(e: any) { | |
PageOverlay.preventDefault(e) | |
} | |
static disableScroll() { | |
if (window.addEventListener) { | |
window.addEventListener('wheel', PageOverlay.wheel, passiveEvents ? { passive: false } : false) | |
} | |
document.onkeydown = PageOverlay.keydown | |
PageOverlay.disableScrollMobile() | |
} | |
static enableScroll() { | |
if (window.removeEventListener) { | |
window.removeEventListener('wheel', PageOverlay.wheel, false) | |
} | |
document.onkeydown = null | |
PageOverlay.enableScrollMobile() | |
} | |
static disableScrollMobile() { | |
document.addEventListener('touchmove', PageOverlay.preventDefault, passiveEvents ? { passive: false } : false) | |
} | |
static enableScrollMobile() { | |
document.removeEventListener('touchmove', PageOverlay.preventDefault, false) | |
} | |
/** | |
* Устанавливает отступ от верхнего края страницы | |
* @param offsetTop { Number } - отступ (в пикселях) | |
* @param fixedOffset { Boolean } - скролл (window.pageYOffset) учитываться не будет | |
*/ | |
public setOffsetTop(offsetTop: number, fixedOffset: boolean = false) { | |
this.offsetTop = offsetTop | |
const { body } = document | |
const docEl = document.documentElement | |
const scrollOffset = window.pageYOffset || docEl.scrollTop || body.scrollTop | |
if (this.targetNode) this.targetNode.style.top = fixedOffset ? `${offsetTop}px` : `${offsetTop - scrollOffset}px` | |
} | |
public show() { | |
if (this.targetNode) { | |
this.targetNode.style.opacity = '1' | |
this.targetNode.style.visibility = 'visible' | |
if (this.noContentScroll) PageOverlay.disableScroll() | |
this.hideFooter() | |
} | |
} | |
public hide() { | |
if (this.targetNode) { | |
this.targetNode.style.opacity = '0' | |
this.targetNode.style.visibility = 'hidden' | |
if (this.noContentScroll) PageOverlay.enableScroll() | |
this.showFooter() | |
} | |
} | |
/** | |
* Убивает старые подложки | |
* Нужно вызвать в хуке Vue.js "beforeDestroy" | |
*/ | |
public destroy() { | |
this.destroyOldOverlays() | |
} | |
/** | |
* Главный метод инициализации | |
* Вызывается в конструкторе | |
*/ | |
public init() { | |
if (this.checkIfOverlayExists()) { | |
this.destroyOldOverlays() | |
} else { | |
this.createOverlay() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment