【问题标题】:Is it possible to animate Flexbox inserts & removes?是否可以为 Flexbox 插入和移除设置动画?
【发布时间】:2012-06-21 20:11:15
【问题描述】:

当我从 flexbox 中移除一个项目时,剩余的项目会立即“捕捉”到它们的新位置,而不是动画。

从概念上讲,由于项目正在改变它们的位置,我希望过渡适用。

我已经在所有涉及的元素(弹性框和子元素)上设置了过渡属性

有什么方法可以对弹性框的编辑(添加和删除)进行动画处理?这对我来说实际上是一件大事,也是 flexbox 缺少的一块。

【问题讨论】:

  • 您能否发布一个完全重现您的问题的演示? JS FiddleJS Bin 或类似的都很好,因为我们无需构建自己的测试即可查看您的代码中发生了什么。
  • 尝试(对于 chrome)-webkit-transition:width 2s;我认为这可能是盒子被移除时动画的宽度。

标签: css css-transitions css-animations flexbox


【解决方案1】:

您可以使用 MutationObserver 在添加或删除子项时启动动画。

优势在于,您无需修改​​任何现有代码,包括 Angular、React 或 Blazor 等 SPA 框架(甚至服务器端)。您不必添加人工动画类。

它应该适用于任何布局,而不仅仅是 flexbox。

以下是基于 nice Maurici Abad's answer 使用 MutationObserver:

//add this to your project

function animateChildren(container) {
  function getFlexItemsInfo(container) {
    return Array.from(container.children).map((item) => {
      const rect = item.getBoundingClientRect()
      return {
        element: item,
        x: rect.left,
        y: rect.top,
        width: rect.right - rect.left,
        height: rect.bottom - rect.top,
      }
    })
  }

  function animateFlexItems(oldFlexItemsInfo,  newFlexItemsInfo) {
    for (const newFlexItemInfo of newFlexItemsInfo) {
      const oldFlexItemInfo = oldFlexItemsInfo.find(e => e.element == newFlexItemInfo.element);

      if (!oldFlexItemInfo) {
        continue; 
      }

      const translateX = oldFlexItemInfo.x - newFlexItemInfo.x
      const translateY = oldFlexItemInfo.y - newFlexItemInfo.y
      const scaleX = oldFlexItemInfo.width / newFlexItemInfo.width
      const scaleY = oldFlexItemInfo.height / newFlexItemInfo.height


      newFlexItemInfo.element.animate(
        [
          {
            transform: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`,
          },
          { transform: 'none' },
        ],
        {
          duration: 250,
          easing: 'ease-out',
        }
      )
    }
  }
  
  
  let oldFlexItemsInfo = getFlexItemsInfo(container);
  // Callback function to execute when mutations are observed
  const childListMutationCallback = function(mutationsList, observer) {
      const newFlexItemsInfo = getFlexItemsInfo(container);
      if (oldFlexItemsInfo) {
        animateFlexItems(oldFlexItemsInfo, newFlexItemsInfo);  
      }
      oldFlexItemsInfo = newFlexItemsInfo;
  };

  new MutationObserver(childListMutationCallback).observe(container, { childList: true });
}


  
const container = document.querySelector('.container');
animateChildren(container);

//emulate existing adding/removing items
document.addEventListener('click', e => {
  const item = e.target.closest('.item');
  if (item) {
    e.target.matches('.btn-close') 
      ? container.removeChild(item)
      : container.prepend(item.cloneNode(true));
  }
});
.container {
  display: flex;
  flex-wrap: wrap;
  gap: 2em;
  width: 100%;
  overflow: none;
}
.container>* {
  transform-origin: left top;  
}
  
.container>* {
  flex-grow: 1;
  max-width: 50%;
  min-width: 20%;
  border-radius: 5px;
  height: 5em;
  margin: 0.5rem;
  box-shadow: 0 1px 8px rgba(0,0,0,0.3);
  padding: 1em;
  background: white;
}

.btn-close:after {
  padding: .25em .25em;
  content: 'X';
  border: solid 1px
}
<div class="container">
  <div class="item"> 
    Item 1 <span class="btn-close"></span>
  </div>
  <div class="item" style="background: #f64f59"> 
    Item 2 <span class="btn-close"></span>
  </div>
  <div class="item"  style="background: #c471ed ">
    Click to add <span class="btn-close"></span>
  </div>
</div>

【讨论】:

    【解决方案2】:

    我已经完成了一个代码笔,当你删除一个元素时,它会为元素设置动画,看看: https://codepen.io/MauriciAbad/pen/yLbrpey

    HTML

    <div class="container">
        <div></div>
        <div></div>
        ... more elements ...
    </div>
    

    CSS

    .container{
        display: flex;
        flex-wrap: wrap;
    }
    .container > * {
        transform-origin: left top;
    }
    

    打字稿

    如果您想要 JavaScript,只需从函数的签名和顶部的界面中删除 : Anything

    interface FlexItemInfo {
      element: Element
    
      x: number
      y: number
      width: number
      height: number
    }
    
    const container = document.querySelector('.container')
    for (const item of container.children) {
      item.addEventListener('click', () => {
        removeFlexItem(container, item)
      })
    }
    
    function removeFlexItem(container: Element, item: Element): void {
      const oldFlexItemsInfo = getFlexItemsInfo(container)
      container.removeChild(item)
      const newFlexItemsInfo = getFlexItemsInfo(container)
    
      aminateFlexItems(oldFlexItemsInfo, newFlexItemsInfo)
    }
    
    function getFlexItemsInfo(container: Element): FlexItemInfo[] {
      return Array.from(container.children).map((item) => {
        const rect = item.getBoundingClientRect()
        return {
          element: item,
          x: rect.left,
          y: rect.top,
          width: rect.right - rect.left,
          height: rect.bottom - rect.top,
        }
      })
    }
    
    function aminateFlexItems(
      oldFlexItemsInfo: FlexItemInfo[],
      newFlexItemsInfo: FlexItemInfo[]
    ): void {
      for (const newFlexItemInfo of newFlexItemsInfo) {
        const oldFlexItemInfo = oldFlexItemsInfo.find(
          (itemInfo) => itemInfo.element === newFlexItemInfo.element
        )
    
        const translateX = oldFlexItemInfo.x - newFlexItemInfo.x
        const translateY = oldFlexItemInfo.y - newFlexItemInfo.y
        const scaleX = oldFlexItemInfo.width / newFlexItemInfo.width
        const scaleY = oldFlexItemInfo.height / newFlexItemInfo.height
    
        newFlexItemInfo.element.animate(
          [
            {
              transform: `translate(${translateX}px, ${translateY}px) scale(${scaleX}, ${scaleY})`,
            },
            { transform: 'none' },
          ],
          {
            duration: 250,
            easing: 'ease-out',
          }
        )
      }
    }
    

    【讨论】:

    • 这是一个非常简洁的解决方案。我无法理解的一件事是你在card.parentNode.removeChild(card) 之后打电话给moveCards。在动画开始之前,剩余元素的重新定位仍然没有完成。不应该首先在 flex 容器中立即重新定位元素(由于元素移除),然后开始过渡吗?我想知道为什么它能够像现在这样工作!
    • @D_S_X 是的。元素移动到新位置,然后动画将其移回当前位置。元素移除和动画开始之间有一点差距,但是 js 速度很快,浏览器优化后看起来很流畅。
    • 明白。但是为什么.animate 将它们移回然后开始动画呢?我们正在翻译它们,而不是更改它们的 left/right 值,所以如果它们在元素移除后移动到新位置,难道不应该从这些新位置翻译它们吗?
    【解决方案3】:

    对@skyline3000 和@chris-nicola 的解决方案的另一个改编:http://jsfiddle.net/2gbPg/2/

    您可以简单地为max-width(或max-height,视情况而定)设置动画,删除时设置为0,插入时设置为100%

    【讨论】:

      【解决方案4】:

      我一直在尝试为我的 flexbox 中的行设置动画。仅通过 css 是不可能的。所以我用一点 javascript 和一个额外的父级为我的 flexbox 行做了。

      HTML:

      <div class="outer-container">
        <div class="inner-container">
          <div class="row">Row number 1</div>
        </div>
      </div>
      
      <button id="add">Add one more item</button>
      

      CSS:

      .row{
        height : 40px;
        width : 200px;
        border: 1px solid #cecece;
        text-align : center;
        padding-top : 20px;
      }
      
      .outer-container {
        height : 500px;
        border : 1px solid blue;
        width : 250px;
        display: flex;
        justify-content : center;
        align-items: center;
      }
      
      .inner-container { 
        height : 42px;
        transition : height 0.3s;
       }
      
      button {
        width : 200px;
        height: 30px;
        margin-left : 80px;
        margin-top : 10px;
      }
      

      Javascript:

      (() => {
          let count = 1;
          document.querySelector("#add").addEventListener('click', () => {
              const template = document.createElement('div');
              count += 1;
              template.textContent = `Row number ${count}`;
              template.className = 'row';
      
              const innerContainer = document.querySelector('.inner-container');
              innerContainer.appendChild(template);
              innerContainer.style.height = `${innerContainer.clientHeight + 42}px`;
          })
      })();
      

      工作演示:https://jsfiddle.net/crapbox/dnx654eo/1/

      【讨论】:

        【解决方案5】:

        我不小心让它以一种简单的方式工作。 基本上,你设置width:0;flex-grow:1 当然添加transition:all 2s; 就是这样。这是一个奇怪的 hack。

        See it working

        【讨论】:

        • 宽度技巧对我有用,谢谢!!
        【解决方案6】:

        请记住,灵活盒模型和网格布局规范不断变化,甚至属性和有效值也是如此。浏览器的实现也远未完成。话虽如此,您可以在 flex 属性上进行过渡,以便元素平滑过渡,然后只需侦听 TransitionEnd 即可最终从 DOM 树中删除该节点。

        这是一个示例 JSFiddle,在 Chrome 21 中运行:http://jsfiddle.net/5kJjM/(单击中间的 div)

        var node = document.querySelector('#remove-me');
        
        node.addEventListener('click', function(evt) {
          this.classList.add('clicked');
        }, false);
        
        node.addEventListener('webkitTransitionEnd', function(evt) {
          document.querySelector('#flexbox').removeChild(this);
        }, false);
        #flexbox {
          display: -webkit-flex;
          -webkit-flex-flow: row;
        }
        
        .flexed {
          border: 1px solid black;
          height: 100px;
          -webkit-flex: 1 0 auto;
          -webkit-transition: -webkit-flex 250ms linear;
        }
        
        .clicked {
          -webkit-flex: 0 0 auto;
        }
        <div id="flexbox">
          <div class="flexed"></div>
          <div class="flexed" id="remove-me"></div>
          <div class="flexed"></div>
        </div>

        编辑: 为了进一步说明,当你删除一个节点时,你应该将它的 flex 设置为 0,然后将它从 DOM 中删除。添加节点时,使用 flex:0 添加,然后将其转换为 flex:1

        【讨论】:

        • 在你的中间 div 中有内容的地方,这不太好用
        • 您可以轻松地包装内容并将溢出设置为在过渡期间隐藏,以使其正常工作。
        • 我在演示中找不到任何过渡...它的工作方式与普通 flex 一样...并且演示中的 transitionEnd 处理程序未触发,这意味着未应用任何过渡。 ..
        • 是的,同样的问题。如果这有效,则不再有效。
        【解决方案7】:

        我已经根据Treehouse 的这个示例修复了@skyline3000 的演示。如果浏览器发生变化,不确定这是否会再次中断,但这似乎是为 flex 大小变化设置动画的预期方式:

        http://jsfiddle.net/2gbPg/2/

        我也使用了 jQuery,但从技术上讲,它不是必需的。

        .flexed {
            background: grey;
            /* The border seems to cause drawing artifacts on transition. Probably a browser bug. */
            /* border: 1px solid black; */
            margin: 5px;
            height: 100px;
            flex-grow: 1;
            transition: flex-grow 1000ms linear;
        }
        
        .removed {
            /* Setting this to zero breaks the transition */
            flex-grow: 0.00001;
        }
        

        关于 CSS 需要注意的一点是,您不能过渡到零的 flex-grow,它不会过渡,它只会消失。您只需要输入一个非常小的值。绘制边框时似乎也存在人为错误,因此我在这种情况下使用了背景。

        【讨论】:

        • 对从中心到 flex-start 的动画 Justify-Content 做什么
        • 我不太确定我明白你的意思,你能举个例子吗?
        • 垂直布局可以做到这一点吗?即 flex-flow: column;
        • 这似乎只有在没有内容的情况下才有效,这似乎......不太可能。 (例如,在 div 中添加一些文本)
        • 当元素是图像时它也不起作用
        猜你喜欢
        • 2015-05-03
        • 2019-09-04
        • 2015-10-22
        • 2012-01-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多