纯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. 效果展示

弹窗组件 jquery 弹窗组件js_选择器

这么简单好用,还不快快用起来吗,当然还是有很多的不足,也希望有人能够提出来,大家多多交流,互相促进~~