How to create a React Modal(which is append to `<body>`) with transitions?

后端 未结 8 1018
遥遥无期
遥遥无期 2020-12-07 08:23

There is a modal in this answer https://stackoverflow.com/a/26789089/883571 which is creating a React-based Modal by appending it to . However, I fo

相关标签:
8条回答
  • 2020-12-07 08:30

    I've written a library to help with this. I avoid the DOM insertion hacks used by Portal strategies out there and instead make use of context based registries to pass along components from a source to a target.

    My implementation makes use of the standard React render cycles. The components that you teleport/inject/transport don't cause a double render cycle on the target - everything happens synchronously.

    The API is also structured in a manner to discourage the use of magic strings in your code to define the source/target. Instead you are required to explicitly create and decorate components that will be used as the target (Injectable) and the source (Injector). As this sort of thing is generally considered quite magical I think explicit Component representation (requiring direct imports and usage) may help alleviate confusion on where a Component is being injected.

    Although my library won't allow you to render as a direct child of the document.body you can achieve an acceptable modal effect by binding to a root level component in your component tree. I plan on adding an example of this use case soon.

    See https://github.com/ctrlplusb/react-injectables for more info.

    0 讨论(0)
  • 2020-12-07 08:35

    As other answers have stated this can be done using Portals. Starting from v16.0 Portals are included in React.

    <body>
      <div id="root"></div>
      <div id="portal"></div>
    </body>
    

    Normally, when you return an element from a component's render method, it's mounted into the DOM as a child of the nearest parent node, but with portals you can insert a child into a different location in the DOM.

    const PortalComponent = ({ children, onClose }) => {
      return createPortal(
        <div className="modal" style={modalStyle} onClick={onClose}>
          {children}
        </div>,
        // get outer DOM element
        document.getElementById("portal")
      );
    };
    
    class App extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          modalOpen: false
        };
      }
    
      render() {
        return (
          <div style={styles}>
            <Hello name="CodeSandbox" />
            <h2>Start editing to see some magic happen {"\u2728"}</h2>
            <button onClick={() => this.setState({ modalOpen: true })}>
              Open modal
            </button>
            {this.state.modalOpen && (
              <PortalComponent onClose={() => this.setState({ modalOpen: false })}>
                <h1>This is modal content</h1>
              </PortalComponent>
            )}
          </div>
        );
      }
    }
    
    render(<App />, document.getElementById("root"));
    

    Check working example here.

    0 讨论(0)
  • 2020-12-07 08:39

    Hope it helps. This is my current implementation of a transition modal based on the anwser above:

      React = require 'react/addons'
    
      keyboard = require '../util/keyboard'
      mixinLayered = require '../mixin/layered'
    
      $ = React.DOM
      T = React.PropTypes
      cx = React.addons.classSet
    
      module.exports = React.createFactory React.createClass
        displayName: 'body-modal'
        mixins: [mixinLayered]
    
        propTypes:
          # this components accepts children
          name:             T.string.isRequired
          title:            T.string
          onCloseClick:     T.func.isRequired
          showCornerClose:  T.bool
          show:             T.bool.isRequired
    
        componentDidMount: ->
          window.addEventListener 'keydown', @onWindowKeydown
    
        componentWillUnmount: ->
          window.removeEventListener 'keydown', @onWindowKeydown
    
        onWindowKeydown: (event) ->
          if event.keyCode is keyboard.esc
            @onCloseClick()
    
        onCloseClick: ->
          @props.onCloseClick()
    
        onBackdropClick: (event) ->
          unless @props.showCornerClose
            if event.target is event.currentTarget
              @onCloseClick()
    
        renderLayer: ->
          className = "body-modal is-for-#{@props.name}"
          $.div className: className, onClick: @onBackdropClick,
            if @props.showCornerClose
              $.a className: 'icon icon-remove', onClick: @onCloseClick
            $.div className: 'box',
              if @props.title?
                $.div className: 'title',
                  $.span className: 'name', @props.title
                  $.span className: 'icon icon-remove', @onCloseClick
              @props.children
    
        render: ->
          $.div()
    
    0 讨论(0)
  • 2020-12-07 08:40

    At react conf 2015, Ryan Florence demonstrated using portals. Here's how you can create a simple Portal component...

    var Portal = React.createClass({
      render: () => null,
      portalElement: null,
      componentDidMount() {
        var p = this.props.portalId && document.getElementById(this.props.portalId);
        if (!p) {
          var p = document.createElement('div');
          p.id = this.props.portalId;
          document.body.appendChild(p);
        }
        this.portalElement = p;
        this.componentDidUpdate();
      },
      componentWillUnmount() {
        document.body.removeChild(this.portalElement);
      },
      componentDidUpdate() {
        React.render(<div {...this.props}>{this.props.children}</div>, this.portalElement);
      }
    });
    

    and then everything you can normally do in React you can do inside of the portal...

        <Portal className="DialogGroup">
           <ReactCSSTransitionGroup transitionName="Dialog-anim">
             { activeDialog === 1 && 
                <div key="0" className="Dialog">
                  This is an animated dialog
                </div> }
           </ReactCSSTransitionGroup>
        </Portal> 
    

    jsbin demo

    You can also have a look at Ryan's react-modal, although I haven't actually used it so I don't know how well it works with animation.

    0 讨论(0)
  • 2020-12-07 08:40

    React 15.x

    Here's an ES6 version of the method described in this article:

    import React from 'react';
    import ReactDOM from 'react-dom';
    import PropTypes from 'prop-types';
    
    export default class BodyEnd extends React.PureComponent {
    
        static propTypes = {
            children: PropTypes.node,
        };
    
        componentDidMount() {
            this._popup = document.createElement('div');
            document.body.appendChild(this._popup);
            this._render();
        }
    
        componentDidUpdate() {
            this._render();
        }
    
        componentWillUnmount() {
            ReactDOM.unmountComponentAtNode(this._popup);
            document.body.removeChild(this._popup);
        }
    
        _render() {
            ReactDOM.render(this.props.children, this._popup);
        }
    
        render() {
            return null;
        }
    }
    

    Just wrap any elements you want to be at the end of the DOM with it:

    <BodyEnd><Tooltip pos={{x,y}}>{content}</Tooltip></BodyEnd>
    

    React 16.x

    Here's an updated version for React 16:

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    export default class BodyEnd extends React.Component {
    
        constructor(props) {
            super(props);
            this.el = document.createElement('div');
            this.el.style.display = 'contents'; // The <div> is a necessary container for our content, but it should not affect our layout. Only works in some browsers, but generally doesn't matter since this is at the end anyway. Feel free to delete this line.
        }
    
        componentDidMount() {
            document.body.appendChild(this.el);
        }
    
        componentWillUnmount() {
            document.body.removeChild(this.el);
        }
    
        render() {
            return ReactDOM.createPortal(
                this.props.children,
                this.el,
            );
        }
    }
    

    Working example

    0 讨论(0)
  • 2020-12-07 08:51

    I wrote the module react-portal that should help you.

    0 讨论(0)
提交回复
热议问题