react-transition-group 没有 slideUp 组件 React.js

react-transition-group doesnt slideUp component React.js

我有一个组件,当单击按钮时,带有更多信息的 div 将向上滑动和向下滑动。 下面代码和css风格

import { CSSTransition } from "react-transition-group";
const Card = () => {
  const [showMoreInfo, setShowMoreInfo] = useState(false);

  return (
    <div className="Card">
      <ButtonShowMore isOpen={showMoreInfo} click={() => setShowMoreInfo(!showMoreInfo)} />
      <CSSTransition in={showMoreInfo} classNames="Card-Details" timeout={1000}>
        <div>
          {showMoreInfo && (
            <>
              <p>details</p>
              <p>details</p>
            </>
          )}
        </div>
      </CSSTransition>
    </div>
  );
};

.Card-Details-enter {
  height: 0px;
}

.Card-Details-enter-active {
  height: 100%;
  -webkit-transition: height 1s ease;
  -moz-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}

.Card-Details-enter-done {
  height: 100%;
}

.Card-Details-exit {
  height: 100%;
}
.Card-Details-exit-active {
  height: 0px;
  -webkit-transition: height 1s ease;
  -moz-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
}
.Card-Details-exit-done {
  height: 0px;
}

但是没有用,不知道为什么。我试图将过渡到父元素,如 并向 class *-exit-done 添加过渡,如 ,但没有任何帮助。

它不起作用的原因是百分比高度的转换不是您所期望的。

CSS中的百分比高度和宽度是指他们parent的高度和宽度,而不是他们自己的高度。

MDN Percentage

The CSS data type represents a percentage value. It is often used to define a size as relative to an element's parent object. Numerous properties can use percentages, such as width, height, margin, padding, and font-size.

相同元素使用百分比过渡的示例 height/width。一个有一个 height/width 为 50px 的容器元素,另一个没有。

div.container {
  width: 50px;
  height: 50px;
}

div.transition {
  width: 100%;
  height: 100%;
  background: red;
  transition: width 2s, height 4s;
}

div.transition:hover {
  width: 300%;
  height: 500%;
}
Transition div with a 50px container
<div class="container">
  <div class="transition">
    <p>test</p>
  </div>
</div>

Transition div without a container:
<div class="transition">
  <p>test</p>
</div>


我们真正想要的是从 0px 过渡到自动高度。不幸的是,浏览器不支持自动高度转换。

Using CSS Transitions Auto Dimensions 对此有一个很好的描述,包括一些获得您想要的东西的方法及其缺点。

Why hasn’t this problem been fixed at the browser level?

According to the Mozilla Developer Network docs, auto values have been intentionally excluded from the CSS transitions spec. It looks like it’s been requested by a few people, but when you think about it, it makes at least a little sense that it hasn’t been included. The browser process that re-calculates the sizes and positions of all elements based on their content and the way they interact with each other (known as “reflow”) is expensive. If you were to transition an element into a height of auto, the browser would have to perform a reflow for every stage of that animation, to determine how all the other elements should move. This couldn’t be cached or calculated in a simple way, since it doesn’t know the starting and/or ending values until the moment the transition happens. This would significantly complicate the math that has to be done under the hood and probably degrade performance in a way that might not be obvious to the developer.

How can I transition height: 0; to height: auto; using CSS? 也有一些很好的解决方法,尽管确实没有灵丹妙药。

这绝对是一个众所周知的问题,有一个 request 规范可以更改以允许自动转换,但我认为它还没有消失。


关于对您在 React Transition Group 中从事的转换类型的支持:

Slide Down Animation and Trying to fade out element then slide up both have the same answer overall: pointing at how React Bootstrap's Collapse component 做到了。

您需要依靠找到 dom 节点的实际高度并将其用作过渡的一部分:

  getDimension() {
    return typeof this.props.dimension === 'function'
      ? this.props.dimension()
      : this.props.dimension;
  }

  // for testing
  _getScrollDimensionValue(elem, dimension) {
    return `${elem[`scroll${capitalize(dimension)}`]}px`;
  }

  /* -- Expanding -- */
  handleEnter = (elem) => {
    elem.style[this.getDimension()] = '0';
  }

  handleEntering = (elem) => {
    const dimension = this.getDimension();
    elem.style[dimension] = this._getScrollDimensionValue(elem, dimension);
  }

  handleEntered = (elem) => {
    elem.style[this.getDimension()] = null;
  }

  /* -- Collapsing -- */
  handleExit = (elem) => {
    const dimension = this.getDimension();
    elem.style[dimension] = `${this.props.getDimensionValue(dimension, elem)}px`;
    triggerBrowserReflow(elem);
  }

  handleExiting = (elem) => {
    elem.style[this.getDimension()] = '0';
  }


使用 Collapse class 中的功能的快速粗略示例,作为代码的工作示例,使用功能较不完整的解决方案(注意,主要基于上面链接的 Collapse.js 代码):

const { Transition } = ReactTransitionGroup;
const { EXITED, ENTERED, ENTERING, EXITING } = Transition;
const { useState } = React;

// Quick and dirty classNames functionality
const classNames = (...names) => names.filter((name) => name).join(' ');

const ButtonShowMore = ({ isOpen, click }) => {
  return <button onClick={click}>{isOpen ? 'Close' : 'Open'}</button>;
};

// Heavily based on https://github.com/react-bootstrap/react-bootstrap/blob/next/src/Collapse.js#L150
// for the purpose of demonstration without just pulling in the module:

function triggerBrowserReflow(node) {
  node.offsetHeight; // eslint-disable-line no-unused-expressions
}

const collapseStyles = {
  [EXITED]: 'collapse',
  [EXITING]: 'collapsing',
  [ENTERING]: 'collapsing',
  [ENTERED]: 'collapse in',
};
const Collapse = ({ children, ...props }) => {
  const handleEnter = (elem) => (elem.style.height = '0');
  const handleEntering = (elem) =>
    (elem.style.height = `${elem.scrollHeight}px`);
  const handleEntered = (elem) => (elem.style.height = null);
  const handleExit = (elem) => {
    elem.style.height = `${elem.scrollHeight}px`;
    triggerBrowserReflow(elem);
  };
  const handleExiting = (elem) => (elem.style.height = '0');
  return (
    <Transition
      {...props}
      onEnter={handleEnter}
      onEntering={handleEntering}
      onEntered={handleEntered}
      onExit={handleExit}
      onExiting={handleExiting}
    >
      {(state, innerProps) =>
        React.cloneElement(children, {
          ...innerProps,
          className: classNames(
            props.className,
            children.props.className,
            collapseStyles[state]
          ),
        })
      }
    </Transition>
  );
};

const Card = () => {
  const [showMoreInfo, setShowMoreInfo] = useState(false);

  return (
    <div className="Card">
      <ButtonShowMore
        isOpen={showMoreInfo}
        click={() => setShowMoreInfo(!showMoreInfo)}
      />
      <Collapse in={showMoreInfo} className="Card-Details" timeout={1000}>
        <div style={{ height: 0 }}>
          <p>details</p>
          <p>details</p>
        </div>
      </Collapse>
    </div>
  );
};

ReactDOM.render(<Card />, document.querySelector('#root'));
.collapsing {
  -webkit-transition: height 1s ease;
  -moz-transition: height 1s ease;
  -o-transition: height 1s ease;
  transition: height 1s ease;
  overflow: hidden;
}

.collapse {
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-transition-group/4.4.1/react-transition-group.min.js"></script>

<div id="root" />