Skip to content

Instantly share code, notes, and snippets.

@chy4egg
Created July 1, 2019 12:10
Show Gist options
  • Save chy4egg/f2d93b76c4e92302d8e50f488c5b88f2 to your computer and use it in GitHub Desktop.
Save chy4egg/f2d93b76c4e92302d8e50f488c5b88f2 to your computer and use it in GitHub Desktop.
A typescript page-overlay with scroll-blocking feature
// 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