组件 -可移动modal

前沿

写这份文档也是基于最近开发业务采集平台中使用 antd modal 组件,使用这个组件完成新增表单的弹窗,然后再做这个项目时候,觉得弹窗能够移动好像是个不错的用户体验。于是自己也就重新写了个这样的组件。

为什么写这个组件

1.antd modal 不能移动。
2.弹出动画在长期的开发过程视觉疲脑,虽然这个效果不错。
3.组件的一劳永逸是最为重要

想要什么效果

在这里插入图片描述
首先来看这样一张图很清楚一个标准的弹框这些是必不可少的

  1. 弹框的标题
  2. 弹窗内部子元素
  3. 弹窗的一个满屏背景
  4. 关闭 x 按钮
  5. 关闭按钮
  6. 确认/提交按钮
    那么这些是必须有的属性,要做到高复用,我们应该让 1,2,5,6 作为参数传入;
    这些是必须需要的,另外,可以让组件有更加好的延展性,也可以传入其他的参数比如:
  7. modal 的高度
  8. modal 的宽度
  9. modal 的背景色
  10. 。。。等等

想要怎么使用

毋庸置疑好用才是封装组件的初衷通过整体思路希望是如下效果

<CanMoveModal 
  isShow={visible} // 通过参数判断开始是否展示
  onOk={onOk}    // 确认的回调
  onCancel={onClose} //关闭按钮和'X'按钮的回调  
  title='这是标题'  // 不传默认为空
  okText='确认'    // 不传默认 确认 文案
  cancelText='关闭' // 不传默认 关闭 文案
>
  <Input type="text"/>
</CanMoveModal>

封装

/**
 * @desc: 自定义可移动模板/弹窗 Modal
 * @name: CanMoveModal
 * @author: ZM
 */
import React, { Component } from 'react';
import { Button, Icon } from 'antd';
import PropTypes from 'prop-types';
import Styles from './styles.less';

class CanMoveModal extends Component {
  constructor(props) {
    super(props);
    const { isShow, width = 520 } = props;
    const { clientWidth } = document.documentElement;
    this.state = {
      isShow,
      pageX:(clientWidth - width)/2,
      pageY:100,
      moving: false,
    };
  }
  componentWillReceiveProps({isShow}) {
    
      this.setState({ isShow });
  }
  componentDidMount() {
    this.resize();
    window.addEventListener('resize', this.resize);
  }
  resize = () => {
    const {clientWidth, clientHeight} = document.documentElement;
    const modal = document.getElementById('modal');
    if (modal) {
      const pageY = (clientHeight - modal.offsetHeight) / 2;
      const pageX = (clientWidth - modal.offsetWidth) / 2;
      this.setState({ pageX, pageY});
    }
  }
  onCancel = () => {
    const {onCancel} = this.props;
    if (onCancel) {
      onCancel();
    } else {
      this.setState({ isShow: false });
    }
  }
  open = () => {
    this.setState({ isShow: true });
  }
  // 获取鼠标点击title时的坐标、title的坐标以及两者的位移
  getPosition = (e) => {
    // 标题DOM元素titleDom
    const titleDom = e.target;
    // titleDom的坐标
    const X = titleDom.getBoundingClientRect().left;
    const Y = titleDom.getBoundingClientRect().top;
    // 鼠标点击的坐标
    let mouseX = 0, mouseY = 0;
    if (e.pageX || e.pageY) {  // ff,chrome等浏览器
      mouseX = e.pageX;
      mouseY = e.pageY;
    } else {
      mouseX = e.clientX + document.body.scrollLeft - document.body.clientLeft;
      mouseY = e.clientY + document.body.scrollTop - document.body.clientTop;
    }
    // 鼠标点击位置与modal的位移
    const diffX = mouseX - X;
    const diffY = mouseY - Y;
    return {X, Y, mouseX, mouseY, diffX, diffY};
  }
  /**
   * 鼠标按下,设置modal状态为可移动,并注册鼠标移动事件
   * 计算鼠标按下时,指针所在位置与modal位置以及两者的差值
   **/
  onMouseDown = (e) => {
    const position = this.getPosition(e);
    window.onmousemove = this.onMouseMove;
    this.setState({moving: true, diffX: position.diffX, diffY: position.diffY});
  }

  // 松开鼠标,设置modal状态为不可移动,
  onMouseUp = () => {
    this.setState({ moving: false });
  }
  // 鼠标移动重新设置modal的位置
  onMouseMove = (e) => {
    const { moving, diffX, diffY } = this.state;
    if (moving) {
      // 获取鼠标位置数据
      const position = this.getPosition(e);
      // 计算modal应该随鼠标移动到的坐标
      const x = position.mouseX - diffX;
      const y = position.mouseY - diffY;
      // 窗口大小
      const {clientWidth, clientHeight} = document.documentElement;
      const modal = document.getElementById('modal');
      if (modal) {
        // 计算modal坐标的最大值
        const maxHeight = clientHeight - modal.offsetHeight;
        const maxWidth = clientWidth - modal.offsetWidth;
        // 判断得出modal的最终位置,不得超出浏览器可见窗口
        const left = x > 0 ? (x < maxWidth ? x : maxWidth) : 0;
        const top = y > 0 ? (y < maxHeight ? y : maxHeight) : 0;
        this.setState({pageX: left, pageY: top});
      }
    }
  }
  render() {
    const {okText, cancelText, onOk, children, width = 520, height, title, animatedClass} = this.props;
    const {isShow, pageX, pageY} = this.state;
    const modal = (
      <div className={`${Styles.modal_mask} animated ${animatedClass}`}>
        <div
          id='modal'
          className={Styles.modal_body}
          style={{
            width,
            height: height ? height : 'unset',
            marginLeft: pageX,
            marginTop: pageY,
          }}>
          <div className={Styles.modal_header}>
            <span
              onMouseDown={this.onMouseDown}
              onMouseUp={this.onMouseUp}
              className={Styles.modal_move}
            ></span>
            <span className={Styles.modal_title}>{title ? title : null}</span>
            <div className={Styles.modal_header_close} onClick={this.onCancel}>
              <Icon type='close' />
            </div>
          </div>
          <div className={Styles.modal_content}>
            {children}
          </div>
          <div className={Styles.modal_footer}>
            <div className={Styles.modal_footer_inner}>
              <Button onClick={this.onCancel}>
                {cancelText ? cancelText : '取消'}
              </Button>
              <Button type='primary' onClick={onOk.bind(null)} style={{marginLeft: '10px'}} >
                {okText ? okText : '确定'}
              </Button>
            </div>
          </div>
        </div>
      </div>
    );
    return isShow && modal
  }
}
CanMoveModal.propTypes = {
  isShow: PropTypes.bool,
  title: PropTypes.string,
  width: PropTypes.number,
  height: PropTypes.any,
  okText: PropTypes.string,
  cancelText: PropTypes.string,
  onCancel: PropTypes.func,
  onOk: PropTypes.func,
  animatedClass: PropTypes.string,
};

export default CanMoveModal;

/** 自定义modal相关样式 ***/
.modal_mask {
  position: fixed;
  overflow: auto;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1000;
  background-color: rgba(0, 0, 0, .65);
  -webkit-overflow-scrolling: touch;
  outline: 0;
}
 
.modal_body {
  background: #fff;
  position: relative;
  border-radius: 5px;
  min-width: 400px;
}
 
.modal_header {
  position: relative;
  width: 100%;
  height: 48px;
  line-height: 48px;
  cursor: move;
  text-align: left;
  padding-left: 20px;
  border-bottom: 1px solid #e3e3e3;
}
 
.modal_header_close {
  width: 48px;
  height: 48px;
  cursor: pointer;
  border: 0;
  background: transparent;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 10;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  -webkit-transition: color .3s ease;
  transition: color .3s ease;
  color: rgba(0,0,0,.43);
  outline: 0;
  text-align: center;
  line-height: 48px;
}
 
.modal_content {
  top: 0;
  width: 100%;
  bottom: 50px;
  overflow: auto;
  padding: 10px 20px;
  text-overflow: ellipsis;
  background-color: #fff;
  text-align: left;
  max-height: 80vh;
  min-height: 50px;
}
 
.modal_footer {
  bottom: 0;
  width: 100%;
  height: 46px;
  background: #fff;
  border-top: 1px solid #e3e3e3;
  border-radius: 0 0 5px 5px;
}
 
.modal_footer_inner {
  right: 8px;
  bottom: 8px;
  position: absolute;
}

.modal_title {
  margin: 0;
  font-size: 14px;
  line-height: 21px;
  font-weight: 500;
  color: rgba(0,0,0,.85)
}

.modal_move{
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 50px;
  user-select:none;
}

最后在加入了动画通过 animatedClass 参数来修改动画的效果

npm install animate.css

然后再引入 import ‘animate.css’;
通过再需要的 dom 元素上直接增加类名 class = ‘animated xxx’ xxx代表动画的效果,切记 class 类名一定需要加 animated

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值