4

问题:

我有一个递归渲染嵌套列表的 React 功能组件。那部分工作正常。

我正在努力的部分是让包含嵌套列表的 div 在嵌套列表(不同大小)出现和消失时具有扩展动画。

因为内容的数量是未知的,所以简单地为 max-height 属性设置动画是行不通的。例如,当渲染一级列表时,框架的高度可能会扩大到 100 像素。但是,如果您将最大高度设置为 100 像素,则 div 稍后将无法扩展以容纳越来越多的嵌套列表。

需要动画的div是:

<div className="frame"> [nested ordered lists] </div>

...并且不起作用的函数是名为“collapseFrame”的函数。

同样,我之前尝试在 max-height 属性上使用 CSS 过渡,但这远不是一个理想的解决方案,因为它在不可预测的高度上效果不佳。所以,如果可能的话,我不想那样做。

我遵循了一个教程,并在没有 React 的情况下使用 vanilla JavaScript(部分代码被注释掉),但是当将它翻译成 React 时,我不确定为什么我不能让代码工作。

目前,展开动画正在工作,但如果移除边框或将其更改为白色,它将停止工作。我不知道为什么。??

** 此外,折叠动画根本不起作用。'transitioned' 事件并不总是在 ref 对象上触发,有时会在它应该被删除后触发。

有人能帮我指出正确的方向吗?谢谢!

这是我的代码笔。

(我试图将它转移到 JS Fiddle,但它不起作用,所以请不要对此投反对票)。

https://codepen.io/maiya-public/pen/ZEzoqjW

(对 codepen 的另一次尝试,供参考。嵌套列表出现和消失,但没有过渡)。 https://codepen.io/maiya-public/pen/MWgGzBE

这是原始代码:(我认为在 codepen 上更容易理解,但粘贴在这里以获得良好的实践)。

索引.html

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

样式.css

.frame {
    overflow:hidden;
    transition: all 0.5s ease-out;
    height:auto;
//  for some reason,without the border, not all of them will transition AND it can't be white !?? 
    border: solid purple 1px; 
}

button {
   margin: 0.25rem;
}

虚拟数据:(嵌套列表将基于此对象呈现)

let data = { 
 text: 'list',
  children: [        
  {
text: "groceries",
children: [
  {
    text: "sandwich",
    children: [
      {
        text: "peanut butter",
        children: [{text: 'peanuts', children: [{text: 'nut family'},{text: 'plant', children: [{text: 'earth'}]}] }]
      },
      {
        text: "jelly",
        children: [
          { text: "strawberries", children: null },
          { text: "sugar", children: null }
        ]
      }
    ]
  }
]
  },
    {
    text: "flowers",
    children: [
      {
        text: "long stems",
        children: [
          {
            text: "daisies",
            children: null
          },
          {
            text: "roses",
            children: [
              { text: "pink", children: null },
              { text: "red", children: null }
            ]
          }
        ]
      }
    ]
  }
] };

反应代码:index.js

// component recursively renders nested lists.  Every list item is a list.
const ListItem = ({item, depth}) => {
//   depth prop allows me to give a className depending on  how deeply it is nested, and do CSS style based on that
  let { text, children } = item
  let [showChildren, setShowChildren] = React.useState(false)
  let frame = React.useRef()

  const expandFrame = (frame) => {
      //let frameHeight = frame.style.height //was using this at one point, but not anymore b/c not working
      // was supposed to have frame.style.height = frameHeight + 'px'
      frame.style.height = 'auto'
      frame.addEventListener('transitionend', () =>{
        frame.removeEventListener('transitionend', arguments.callee)
        frame.style.height = null
     })
  }

  const collapseFrame = (frame) => {
   let frameHeight = frame.scrollHeight;

 // temporarily disable all css transitions
  let frameTransition = frame.style.transition;
  frame.style.transition = ''

  requestAnimationFrame(function() {
    frame.style.height = frameHeight + 'px';
    frame.style.transition = frameTransition;
    requestAnimationFrame(function() {
      frame.style.height = 0 + 'px';
    })
    })
      }

  return(
<ol> 
      <button onClick={(e)=>{
          if(!showChildren) {
              // console.log('children not showing/ expand')
             setShowChildren(true)
            expandFrame(frame.current)
          } else {
            // console.log('children showing/ collapse')
             setShowChildren(false)
             collapseFrame(frame.current)
          }
        }}
        >
        {text}-{depth}  
        {  children && <i className="fas fa-sort-down"></i> || children && <i className="fas fa-sort-up"></i>} 
    </button>
      
  {/*THIS IS THE ELEMENT BEING ANIMATED:*/}
  <div className={`frame depth-${depth}`} ref={frame}>
    
        {showChildren && children && children.map(item => {
          return (
            <li key={uuid()}>
                <ListItem item={item} depth={depth + 1}/>
            </li>)
          })
      }
    
    </div>
 </ol>
  )
}

class App extends React.Component {


  render() {
    return (
      <div>
         <ListItem key={uuid()} item={data} depth={0} />
     </div>
    );
  }
}

function render() {
  ReactDOM.render(<App />, document.getElementById("root"));
}

render();
4

1 回答 1

6

我认为如果您使用动画库会容易得多。我重构了您的代码以使用 react-spring。这样代码就干净多了。

ListItem 组件:

const ListItem = ({ item, depth }) => {
  //   depth prop allows me to give a className depending on  how deeply it is nested, and do CSS style based on that
  let { text, children } = item;
  let [showChildren, setShowChildren] = React.useState(false);
  const transition = useTransition(
    showChildren ? children : [],
    item => item.text,
    {
      from: { opacity: 0, transform: 'scaleY(0)', maxHeight: '0px' },
      enter: { opacity: 1, transform: 'scaleY(1)', maxHeight: '1000px' },
      leave: { opacity: 0, transform: 'scaleY(0)', maxHeight: '0px' }
    }
  );

  return (
    <ol>
      <button
        onClick={e => {
          item.children && setShowChildren(!showChildren);
        }}
      >
        {text}-{depth}
        {(children && <i className="fas fa-sort-down" />) ||
          (children && <i className="fas fa-sort-up" />)}
      </button>

      <div className={`frame depth-${depth}`}>
        {transition.map(({ item, key, props }) => (
          <animated.li key={key} style={props}>
            <ListItem item={item} depth={depth + 1} />
          </animated.li>
        ))}
      </div>
    </ol>
  );
};

一点解释:您可以指定进入和离开样式。当新节点添加到列表时调用 Enter。删除时留下调用。并且所有样式都是动画的。一个问题仍然存在,如果我将 maxHeight 值设置得太高,那么高度动画就会太快。如果我将它设置得太低,那么可能会限制具有太多子元素的列表元素的外观。1000px 是一种折衷方案,适用于本示例。它可以通过为其他样式属性设置动画或使用递归函数为每个节点计算 maxHeight 值来解决。

工作示例:https ://codesandbox.io/s/animated-hierarchical-list-5hjhz

于 2019-09-13T12:22:23.883 回答