纯js弹窗Dialog组件
文章目录
- 纯js弹窗Dialog组件
- 1. js部分
- 2. css样式
- 3. 简单使用
- 4. 效果展示
1. js部分
/* eslint-disable */
const {
isTmplValid,
createDom,
getDom,
createSingleDom,
cssFromObj,
addNode,
isDOM
} = require('./Common')
/**
* 根据模板床建
* @param title
*/
function createTmpl(title) {
const titleWrapper = createDom('hxl-dialog-title-wrapper')
if (isTmplValid(title)) {
titleWrapper.innerHTML = title
} else {
titleWrapper.innerText = title
}
return titleWrapper
}
/**
* 根据content创建一个弹窗内容体
* @param content
*/
function createDialogBody(content) {
// 如果为选择器则将选择器对应的元素返回
let res
try {
res = getDom(content)
} catch (err) {
}
if (!!res) {
return res
}
let tmpl = createDom()
if (isTmplValid(content)) {
tmpl.innerHTML = content
} else {
tmpl.innerText = content
}
res = isTmplValid(content) ? tmpl.firstElementChild : document.createTextNode(content)
tmpl = null
return res
}
/**
* Dialog 弹出框
*/
class Dialog {
constructor (config) {
this.config = {
// 是否显示
visible: false,
// 标题
title: '标题',
// 内容体 如果此处为选择器的话会将选择器内对应的内容直接拉过来
content: '内容区',
// 宽度
width: '50%',
// 其父级元素的选择器
parentSelector: 'body',
// 是否可以移动
moveAble: true,
// 是否需要遮罩
modal: true,
// 点击遮罩关闭
closeOnClickModal: true,
// 点击esc关闭
closeOnPressEscape: true,
// 显示关闭按钮
showClose: true,
// 是否显示头部和底部的分割线
divideLine: {
header: true,
footer: true
},
// 关闭前回调,会阻塞关闭,调用done继续执行关闭
beforeClose: done => done(),
// 底部按钮取消
cancel: (self) => {
self.close()
},
// 底部按钮确认
confirm: (self) => {
self.close()
},
// 自定义底部 为空则没有
footer: `
<div class="hxl-dialog-footer-wrapper">
<div class="hxl-btn pointer" data-role="cancel">取消</div>
<div class="hxl-btn hxl-btn__primary pointer" data-role="confirm">确认</div>
</div>
`
}
Object.assign(this.config, config)
this.init()
}
// 初始化数据
init() {
// 获取父容器窗口 如果窗口不存在则使用body
this.parentNode = getDom(this.config.parentSelector) || getDom('body')
// 如果需要显示蒙版则生成
this.config.modal && this._createBgModal()
// 生成dialog的盒子
this._createContainer()
// 生成头部
this._createHeader()
// 内容区
this._createBody()
// 底部
this.config.footer !== '' && this._createFooter()
this.render()
this.addEventListeners()
}
// 生成背景蒙版
_createBgModal() {
// 如果不存在则创建
this.bgModal || (this.bgModal = createDom('hxl-dialog-modal'))
// 清空内部内容
this.bgModal.innerHTML = ''
}
_createContainer() {
// 生成唯一标识 如果this.id为空则生成对应的id
this.id || (this.id = `hxl-dialog-${Date.now()}`)
// 根据id查找元素
this.container = createSingleDom(this.id)
this.container.classList.add('hxl-dialog')
// 行内样式
this.container.style = cssFromObj({
width: this.config.width
})
// 清空内部的内容
this.container.innerHTML = ''
}
// 生成头部
_createHeader() {
// 头部的div
this.header = createDom('hxl-dialog-header')
this.config.moveAble && this.header.classList.add('move-able')
this.config.divideLine.header && this.header.classList.add('show-divide-line')
// 标题
const title = createTmpl(this.config.title)
// 关闭按钮,因为要对其增加事件监听,绑定到对象上
this.closeBtn = createDom('hxl-close_x pointer')
this.header.appendChild(title)
this.header.appendChild(this.closeBtn)
}
// 生成内容区
_createBody() {
this.body = createDom('hxl-dialog-body')
const content = createDialogBody(this.config.content)
this.body.appendChild(content)
}
// 生成底部
_createFooter() {
this.footer = createDom('hxl-dialog-footer')
this.config.divideLine.footer && this.footer.classList.add('show-divide-line')
if (isTmplValid(this.config.footer)) {
this.footer.innerHTML = this.config.footer
} else {
this.footer.innerText = this.config.footer
}
this.footerBtns = this.footer.querySelectorAll('.hxl-dialog-footer-wrapper .hxl-btn')
}
// 渲染
render() {
// 向父元素中添加
addNode(this.container, this.header, this.body, this.footer)
}
// 添加事件监听
addEventListeners() {
;[...this.footerBtns].forEach(btn => {
btn.onclick = () => {
const type = btn.getAttribute('data-role')
const func = this.config[type]
typeof func === 'function' && func(this)
}
})
// 关闭
this.closeBtn.onclick = () => {
this.close()
}
// 拖动
this.config.moveAble && this.addDragEventListener()
// 遮罩层
if (this.bgModal && this.config.closeOnClickModal) {
this.bgModal.onclick = (e) => {
this.close()
}
}
// esc退出
if (this.config.closeOnPressEscape) {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.close()
}
})
}
}
addDragEventListener() {
if (isDOM(this.header)) {
this.header.onmousedown = (e) => {
// 记录开始位置
this.dragging = true
this.dragStratX = e.clientX
this.dragStratY = e.clientY
}
document.addEventListener('mouseup', () => {
this.dragging = false
})
document.body.addEventListener('mousemove', (e) => {
if (this.dragging) {
const {clientX, clientY} = e
const deltaX = clientX - this.dragStratX
const deltaY = clientY - this.dragStratY
this.move(deltaX, deltaY)
this.dragStratX = clientX
this.dragStratY = clientY
}
})
}
}
move(x, y) {
if (isDOM(this.container)) {
const {offsetLeft, offsetTop} = this.container
const top = offsetTop + y
const left = offsetLeft + x
this.container.style.left = `${left}px`
this.container.style.top = `${top}px`
}
}
// 显示
show() {
// 如果需要蒙版则加上
this.config.modal && addNode(this.parentNode, this.bgModal)
addNode(this.parentNode, this.container)
// this.addEventListeners()
}
close() {
typeof this.config.beforeClose === 'function' && this.config.beforeClose(this.doClose.bind(this))
}
// 关闭
doClose() {
this.bgModal && this.bgModal.remove()
this.container && this.container.remove()
}
get visible() {
return this.config.visible
}
set visible(val) {
// 这里做显隐逻辑
val ? this.show() : this.doClose()
this.config.visible = val
}
}
module.exports = {
Dialog
}
2. css样式
.hxl-dialog-modal {
width: 100vw;
height: 100vh;
background: #000;
opacity: .5;
top: 0;
left: 0;
position: fixed;
z-index: 2048;
}
.hxl-dialog {
$divideColor: #e4e4e4;
width: 50%;
min-height: 30px;
max-height: 100vh;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
position: fixed;
overflow: hidden;
z-index: 2049;
&>div {
padding: 10px 12px;
}
.hxl-dialog-body {
overflow: auto;
}
.hxl-dialog-header {
flex-shrink: 0;
display: flex;
width: 100%;
align-items: center;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
&.move-able {
cursor: move;
}
.hxl-dialog-title-wrapper {
flex: 1;
}
&.show-divide-line {
border-bottom: 1px solid $divideColor;
}
}
.hxl-dialog-footer {
flex-shrink: 0;
&.show-divide-line {
border-top: 1px solid $divideColor;
}
}
.hxl-dialog-footer-wrapper {
text-align: right;
}
}
.hxl-close_x {
width: 20px;
height: 20px;
background-image: url("../img/close.png");
background-repeat: no-repeat;
background-size: cover;
}
.hxl-btn {
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: #fff;
border: 1px solid #dcdfe6;
color: #606266;
-webkit-appearance: none;
text-align: center;
outline: none;
margin: 0;
transition: .1s;
font-weight: 500;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
&.hxl-btn__primary {
color: #fff;
background-color: #409eff;
border-color: #409eff;
&:hover {
background: #66b1ff;
border-color: #66b1ff;
color: #fff;
}
&:active {
background: #3a8ee6;
border-color: #3a8ee6;
color: #fff;
}
}
&:active {
color: #3a8ee6;
border-color: #3a8ee6;
outline: none;
}
&+.hxl-btn {
margin-left: 10px;
}
&:hover {
border-color: #c6e2ff;
background-color: #ecf5ff;
}
}
3. 简单使用
- 使用方式
// 引入
const { LoopImages, Dialog } = require('@/assets/js/util')
// 实例化
this.dialog = new Dialog({
// modal: false,
// 内容部分使用选择器的话会将页面中的元素放到弹窗之中
content: '.loop-img-container',
// 关闭前做点啥,调用done完成关闭
beforeClose: (done) => {
console.log('我要关闭了')
done()
},
// 确认按钮的回调
confirm: (dialog) => {
dialog.close()
}
})
// 显示
this.dialog.show()
// 关闭
this.dialog.close()
- vue中使用
<template>
<div>
<div class="loop-img-container" ref="loopImg"></div>
<div style="margin-top: 150px;text-align: center">
<button class="hxl-btn hxl-btn__primary" @click="showDialog">显示弹窗</button>
</div>
</div>
</template>
<script>
// eslint-disable-next-line no-unused-vars
const { LoopImages, Dialog } = require('@/assets/js/util')
export default {
mounted () {
// eslint-disable-next-line no-unused-vars
const loopImg = new LoopImages(this.$refs.loopImg, [
require('@/assets/logo.png'),
require('@/assets/img/prev.png'),
require('@/assets/logo.png'),
require('@/assets/img/prev.png')
], {
// arrowAlwaysShow: true
})
loopImg.render()
this.dialog = new Dialog({
// modal: false,
content: '.loop-img-container',
beforeClose: (done) => {
console.log('我要关闭了')
done()
},
confirm: (dialog) => {
dialog.close()
}
})
},
methods: {
showDialog () {
this.dialog.show()
}
}
}
</script>
<style scoped>
.btn-group button {
width: 50px;
margin-right: 10px;
/*flex: 1;*/
}
.loop-img-container {
width: 90%;
border: 1px solid #1f1f1f;
height: 200px;
margin: 20px auto;
}
</style>
4. 效果展示
这么简单好用,还不快快用起来吗,当然还是有很多的不足,也希望有人能够提出来,大家多多交流,互相促进~~