纯js弹窗Dialog组件

纯js弹窗Dialog组件

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

在这里插入图片描述

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


版权声明:本文为weixin_45946494原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。