Модал не переходит в поле зрения, если другой переходит из поля зрения - PullRequest
0 голосов
/ 03 апреля 2019

Я пытаюсь перевести всплывающее окно в и из поля зрения, когда оно вызывается и отклоняется.

Первоначально отображаемый как null, при щелчке монтируется скрытый компонент, а затем запускается переход, определенный в CSS, для перехода всплывающего окна в представление.

Во время монтирования компонент регистрирует обработчик щелчков в документе и прослушивает щелчки за пределами всплывающего окна, чтобы сначала вывести его из поля зрения, а затем полностью размонтировать, удалив также прослушиватель событий.

Переходы инициируются изменением атрибута style моего компонента, но я также пытался использовать className, который дал точно такие же результаты.

<code>import { useRef, useState, useEffect } from 'react'

/*
 * Popup
 *
 * - [x] auto-dismiss on click outside without hogging the click event
 *       (i.e. without using `stopPropagation()`)
 * - [ ] transition into and out of view
 *       ! No transition when opening a popup while another is still transitionning out out view
 */
function Popup ({ dismiss }) {
  const popupRef = useRef(null)
  const [style, setStyle] = useState(hiddenStyles)
  useEffect(() => {
    setStyle(showingStyles)
  }, [])
  useEffect(() => {
    global.document.addEventListener('click', onClickOutside, false)
    return () => {
      global.document.removeEventListener('click', onClickOutside, false)
    }
  }, [])
  function onClickOutside (event) {
    if (!popupRef.current.contains(event.target)) {
      setStyle(hiddenStyles)
      setTimeout(dismiss, 900) // TODO Find better way to dismiss (unmount) popup on animation end (and move this responsibility to the Item?)
    }
  }
  return (
    <div
      className='popup'
      ref={popupRef}
      style={style}
    >
      <style jsx>{`
        .popup {
          z-index: 1;
          color: black;
          background: white;
          border: 1px solid black;
          position: absolute;
          transition: opacity 0.9s ease;
        }
      `}</style>
      <pre>{JSON.stringify(style, null, 2)}
) } / * * Всплывающий элемент * * - [x] отображать всплывающее окно только при желании, отключать при отклонении * / const hiddenStyles = {непрозрачность: 0} const ShowingStyles = {непрозрачность: 1} Элемент функции ({id, body}) { const [showActions, setShowActions] = useState (false) function openActions () { setShowActions (правда) } function hideActions () { setShowActions (ложь) } вернуть ( {} Тело {showActions ? ( ) : ноль} ) }

Когда я открываю всплывающие окна по отдельности, выделяя время на то, чтобы закрыть одно, прежде чем открывать следующее, переходы работают. Но если я открою всплывающее окно до того, как другое полностью исчезнет, ​​переход застрянет в своем конечном состоянии с самого начала.

Вопрос: почему?

1 Ответ

0 голосов
/ 04 апреля 2019

Инструмент фрагмента stackoverflow не поддерживает React 16.8, поэтому я реорганизовал свой код с перехватчиков на классы и наткнулся на решение, которое также работает в мире перехватчиков: замена

useEffect(() => {
    setStyle(showingStyles)
  }, [])

с

useEffect(() => {
  setTimeout(() => {
    setStyle(showingStyles)
  }, 10)
}, [])

Я не могу показать полный функциональный код с использованием хуков, но здесь рабочая версия с использованием классов:

/*
 * Popup
 *
 * - [x] auto-dismiss on click outside without hogging the click event
 *       (i.e. without using `stopPropagation()`)
 * - [ ] transition into and out of view
 *       ! No transition when opening a popup while another is still transitionning out out view
 */
class Popup extends React.Component {
  constructor (props) {
    super(props)
    this.onClickOutside = this.onClickOutside.bind(this)
    
    this.popupRef = null
    this.state = {
      style: hiddenStyles
    }
  }
  
  componentDidMount () {
    setTimeout(() => {
      this.setState({ style: showingStyles })
    }, 10)
    document.addEventListener('click', this.onClickOutside, false)
  }
  
  componentWillUnmount () {
    document.removeEventListener('click', this.onClickOutside, false)
  }

  onClickOutside (event) {
    if (!this.popupRef.contains(event.target)) {
      this.setState({ style: hiddenStyles })
      setTimeout(this.props.dismiss, 900) // TODO Find better way to dismiss (unmount) popup on animation end (and move this responsibility to the Item?)
    }
  }
  render () {
    return (
       {
          this.popupRef = el
        }}
        style={this.state.style}
      >
        {JSON.stringify(this.state.style, null, 2)}
) } } / * * Всплывающий элемент * * - [x] отображать всплывающее окно только при желании, отключать при отклонении * / const hiddenStyles = {непрозрачность: 0} const ShowingStyles = {непрозрачность: 1} Класс Item расширяет React.Component { конструктор (реквизит) { супер (реквизит) this.openActions = this.openActions.bind (this) this.hideActions = this.hideActions.bind (this) this.state = { showActions: false } } openActions () { this.setState ({showActions: true}) } hideActions () { this.setState ({showActions: false}) } render () { const {body} = this.props вернуть ( {} Тело {this.state.showActions ? ( ) : ноль} ) } } / * * Приложение * * - [x] Показать список товаров * / const items = [ {id: 'a', тело: 'Green!' }, {id: 'b', тело: 'Green!' }, {id: 'c', тело: 'Yellow?' }, {id: 'd', тело: 'Red'}, {id: 'e', ​​тело: 'Grey'} ] Приложение класса расширяет React.Component { render () { вернуть (
) } } ReactDOM.render (, document.getElementById ('app'))
.row {
  position: relative;
  cursor: pointer;
  padding: 5px;
}
.row:hover {
  background: #d636e9;
  color: #ffe2f0;
}
.popup {
  z-index: 1;
  color: black;
  background: white;
  border: 1px solid black;
  position: absolute;
  transition: opacity 0.9s ease;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app" />

Мне совсем не нравится это решение, потому что я не понимаю, зачем нужен setTimeout, но, по крайней мере, он работает ...

...