React:如何在内容大小未知时为展开和折叠 Div 设置动画

Posted

技术标签:

【中文标题】React:如何在内容大小未知时为展开和折叠 Div 设置动画【英文标题】:React: How to Animate Expanding and Collapsing Div When the Size of the Content is Not Knowable 【发布时间】:2020-01-13 21:07:50 【问题描述】:

问题:

我有一个递归渲染嵌套列表的 React 函数组件。这部分工作正常。

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

由于不知道内容的数量,简单地为 max-height 属性设置动画是行不通的。例如,当渲染一级列表时,框架的高度可能会扩大到 100 像素。但是,如果您将 max-height 设置为 100px 的动画,则 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 上更容易理解,但在这里粘贴以获得良好的实践)。

index.html

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

style.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();

【问题讨论】:

这很难,暂时找不到方法 (codepen.io/joseluismurillorios/pen/gOYzQEa?editors=0010) 感谢您的尝试 :) ...至少我知道不只是我。大声笑。 我遇到了同样的问题,不使用任何库你能做到吗***.com/questions/67861181/… 【参考方案1】:

我认为如果你使用动画库会容易得多。我重构了您的代码以使用 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

【讨论】:

嗨,彼得 - 感谢您发布此消息!了解 react-spring 库是如何工作的非常有帮助。我重新措辞了我的问题,指出没有动画的是列表项周围的 div(不是列表项本身),问题是当没有办法时你不能使用 maxHeight来预测高度。话虽如此,也许将动画移动到列表项是最实用的解决方法。感谢您的提示! 如果数据是已知的,那么你可以用一种相对简单的方法来计算一个节点的所有子节点的数量。如果要在计算中包括打开/关闭状态。您可以将打开状态从项目提升到列表。但是您可以忽略此信息,对于 max-height 它仍然相对较好。 ok .. 我意识到将其移动到 li 标签不会产生影响,因为它们必须像框架一样扩展!我在 React Spring 上环顾四周,看到了一个他们正在做一些事情的例子,所以我会研究它。再次感谢! 不使用包有办法,现在我也没有找到不使用包的合适解决方案 更新到 react-spring 9.4.2 会破坏代码框... 使用以下代码:item =&gt; item.text,

以上是关于React:如何在内容大小未知时为展开和折叠 Div 设置动画的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Qt 中制作可展开/可折叠的部分小部件

如何根据动态内容和最大尺寸限制折叠/展开视图?

展开或折叠时可展开的 UItableview 内容高度不同

如何在 SwiftUI 中制作“显示”式的折叠/展开动画?

Pytorch 展开和折叠:如何将这个图像张量重新组合在一起?

通过使用约束自行调整大小来展开和折叠 TableView 单元格