Skip to content

Instantly share code, notes, and snippets.

@aau8
Created September 4, 2022 14:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aau8/7b28375d103dc96602286e1b250e5f75 to your computer and use it in GitHub Desktop.
Save aau8/7b28375d103dc96602286e1b250e5f75 to your computer and use it in GitHub Desktop.
Popup (Dismal)
<div class="modal" data-modal-id="modal" data-close-on-bg>
<div class="modal__wrap">
<div class="modal__window">
<p>MODAL_TEXT</p>
<button class="burger is-active modal-close" data-modal-close>
<span></span>
<span></span>
<span></span>
</button>
</div>
<div class="modal__bg"></div>
</div>
</div>
import { bodyLock, bodyUnlock } from "../utils/functions.js"
/**
* Модальное окно
*
* INFO: Атрибуты (все атрибуты находятся в св-ве attrs)
* data-modal-id="<id-modal>" - (modalId) каждая модалка имеет этот атрибут, в котором мы указываем ее id
* data-close-on-bg - (modalCloseOnBg) модалка, которая должна закрываться при клике по ее фону, должна иметь этот атрибут
* data-modal-open="<id-modal>" - (btnModalOpen) имеет элемент, при нажатии на который открывается модалка
* data-modal-close="<id-modal || Null>" - (btnModalClose) имеет элемент, при нажатии на который, модальное окно закрывается. Если елемент находится внутри модалки, которую он должен закрыть, в значении атрибута указывать id модалки необязательно (можно оставить его пустым). Значение стоит указывать, если элемент, который должен закрыть модалку, находится вне контейнера с атрибутом data-modal-id
*
* INFO: Свойства
* attrs - (Object) названия атрибутов
* classNames - (Object) названия классов
* modalList - (NodeList) список всех модальных окон (для обновления списка использовать updateModalList())
* openingBtnList - (NodeList) список открывающих кнопок
* modalIsShow - (Boolean) модальное окно показано
* modalShow - (Element) показанное модальное окно
* modalShowId - (String) id показанного модального окна
* keyEsc - (Boolean) закрывать модалки при нажатии клавиши Esc. По умолчанию - true
* useHash - (Boolean) использовать хеш. Если в url указан хеш равный id модалки, модалка откроется. По умолчанию - true
* historyHash - (Boolean) сохранять хеш в истории браузера. Если useHash === false, то historyHash будет равен false. По умолчанию - false
* hash - (String) значение хеша
*
* INFO: Функции
* open(<String || Element>) - метод, открывающий модалку
* close(<String || Element || Null>) - метод, закрывающий модалку. Если скобки оставить пустыми, закроется открытая модалка
* update() - метод, обновляющий список модалок (this.modalList) и список кнопок (this.openingBtnList)
* updateModalList() - метод, обновляющий список модалок (this.modalList)
* updateOpeningBtnList() - метод, обновляющий список кнопок (this.openingBtnList)
*
*
* TODO: Что еще можно сделать
* (Атрибуты data-modal-hash и data-modal-hash-history. В случае если this.useHash === false)
* data-modal-hash - указывается у модалки, которая должна открываться по хешу
* data-modal-hash-history - указывается у модалки, которая должна быть сохранена в истории ( использовать вместе с первым атрибутом )
* Прописать возомжные ошибки
* Анимацию появления с помощью js
* Если указан id модалки при загрузке страницы, то модалка должна открываться без плавной анимации
* События
* Если при this.useHash = true, до открытия модалки в url был указан хеш не принадлежащий ни к одной модалке, то при закрытии модалки в url должен указываться тот самый хеш
* Возможность открытия нескольких модалок
* Закрытие/открытие модалок по таймеру
*/
export default class Modals {
attrs = {
modalId: 'data-modal-id',
modalCloseOnBg: 'data-close-on-bg',
btnModalOpen: 'data-modal-open',
btnModalClose: 'data-modal-close',
}
classNames = {
modalShow: 'is-show',
modalBg: 'modal__bg',
}
modalList = document.querySelectorAll(`[${this.attrs.modalId}]`)
openingBtnList = document.querySelectorAll(`[${this.attrs.btnModalOpen}]`)
openBtn = null
modalIsShow = false
modalShow = null
modalShowId = null
keyEsc = true
useHash = true
historyHash = !this.useHash ? false : false
hash = null
constructor(options) {
this._init()
}
// Открыть модальное окно
open(modal) {
if (typeof modal === 'string') {
modal = document.querySelector(`[${this.attrs.modalId}=${modal}]`)
}
this.modalIsShow = true
this.modalShow = modal
this.modalShowId = modal.dataset.modalId
this._modalBgClose()
modal.classList.add(this.classNames.modalShow)
bodyLock()
// Событие открытия модалки
const _eModalOpenStart = new Event('modal-open')
_eModalOpenStart.data = { ...this }
modal.dispatchEvent(_eModalOpenStart)
}
// Закрыть модальное окно
close(modal) {
if (typeof modal === 'undefined') {
if (this.modalShow != null) {
modal = this.modalShow
}
else {
console.error('[Modals]: Все модальные окна закрыты')
return
}
}
if (typeof modal === 'string') {
modal = document.querySelector(`[${this.attrs.modalId}=${modal}]`)
}
if (this.modalShow.dataset.closeOnBg != undefined) {
this._modalBg.removeEventListener('click', this._bgEvent)
}
// Событие закрытия модалки
const _eModalOpenClose = new Event('modal-close')
_eModalOpenClose.data = { ...this }
modal.dispatchEvent(_eModalOpenClose)
this.modalIsShow = false
this.modalShow = null
this.modalShowId = null
modal.classList.remove(this.classNames.modalShow)
bodyUnlock()
}
// Получить модальное окно
get(modalName) {
if (typeof modalName === 'string') {
return this.modalList.find(e => e.getAttribute(`${this.attrs.modalId}`) === modalName) || null
}
else if (typeof modalName === 'object') {
return modalName.map(modalName => this.modalList.find(e => e.getAttribute(`${this.attrs.modalId}`) === modalName) || null)
}
else {
return this.modalList
}
}
// Обновляет список модалок и кнопок
update() {
this.updateModalList()
this.updateOpeningBtnList()
}
// Обновить список модальных окон
updateModalList() {
this.modalList = document.querySelectorAll(`[${this.attrs.modalId}]`)
}
// Обновить список кнопок, открывающих модальные окна
updateOpeningBtnList() {
this.openingBtnList = document.querySelectorAll(`[${this.attrs.btnModalOpen}]`)
}
// Инизиализация Modal
_init() {
this._btnOpen()
this._btnClose()
if (this.keyEsc) this._keyEscClose()
if (this.useHash) this._watchHash()
}
// Открыть модалку при клике по кнопке c атрибутом this.attrs.btnModalOpen
_btnOpen() {
document.addEventListener('click', e => {
if (e.target.dataset.modalOpen != undefined || e.target.closest(`[${this.attrs.btnModalOpen}]`)) {
const btnOpenModal = e.target.dataset.modalOpen != undefined ? e.target : e.target.closest(`[${this.attrs.btnModalOpen}]`)
this.openBtn = btnOpenModal
this.open(btnOpenModal.dataset.modalOpen)
if (this.useHash) this._setHash()
}
})
}
// Закрыть модалку при клике по кнопке с атрибутом this.attrs.btnModalClose
_btnClose() {
document.addEventListener('click', e => {
if (e.target.dataset.modalClose != undefined || e.target.closest(`[${this.attrs.btnModalClose}]`)) {
if (this.useHash) this._clearHash()
this.close(document.querySelector(`[${this.attrs.modalId}=${this.modalShowId}]`))
}
})
}
// Закрытие модалки при клике по фону. Работает только у модалок, у которых ест атрибут this.attrs.modalCloseOnBg
_modalBgClose() {
if (this.modalShow.dataset.closeOnBg === undefined) return
this._modalBg = this.modalShow.querySelector(`.${this.classNames.modalBg}`)
this._bgEvent = () => {
if (this.useHash) this._clearHash()
this.close(this.modalShow)
}
this._modalBg.addEventListener('click', this._bgEvent, { once: true })
}
// Закрытие модалки при нажатии клавиши Esc
_keyEscClose() {
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
if (this.useHash) this._clearHash()
this.close()
}
})
}
// Следим за хешем
_watchHash() {
this._checkHash()
if (this.historyHash) {
window.addEventListener('hashchange', e => {
this._checkHash()
})
}
}
// Проверка хеша
_checkHash() {
const hash = window.location.hash.replace('#', '')
this.hash = (hash === '') ? null : hash
if (hash != '' && document.querySelector(`[data-modal-id=${hash}]`)) {
this.open(hash)
}
if (hash === '' && this.historyHash && this.modalShow) {
this.close()
}
}
// Установка хеша, равного id модалки
_setHash() {
const href = location.origin + location.pathname + '#' + this.modalShowId
history[this.historyHash ? 'pushState' : 'replaceState']({}, '', href)
}
// Удаление хеша
_clearHash() {
const href = location.href.replace(/#[\w-]+/, '');
history[this.historyHash ? 'pushState' : 'replaceState']({}, '', href)
}
}
.modal {
position: fixed;
z-index: 10;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,.9);
overflow: hidden;
visibility: hidden;
opacity: 0;
transition: $tr;
&.is-show {
visibility: visible;
opacity: 1;
overflow-y: auto;
& .modal__wrap {
transform: translate(0, 0);
}
}
}
.modal__bg {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.modal__wrap {
min-height: 100%;
transform: translate(0, 100vh);
display: flex;
justify-content: center;
align-items: center;
padding: 30px 16px;
transition: $tr;
}
.modal__window {
position: relative;
max-width: 615px;
width: 100%;
background: #fff;
padding: 24px;
}
.modal-close {
position: absolute;
top: 0;
right: 0;
width: 36px;
height: 36px;
transition: $tr;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment