65

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

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

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

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

4

7 回答 7

27

我已经根据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零,它不会过渡它只会消失。您只需要输入一个非常小的值。绘制边框时似乎也存在人为错误,因此在这种情况下我使用了背景。

于 2014-07-26T22:33:53.983 回答
14

请记住,灵活盒模型和网格布局规范不断变化,甚至属性和有效值也是如此。浏览器的实现也远未完成。话虽如此,您可以在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

于 2012-07-03T16:07:37.383 回答
7

我已经完成了一个代码笔,当您删除一个元素时会为元素设置动画,看看: 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',
      }
    )
  }
}
于 2018-12-04T17:35:39.183 回答
5

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

看到它工作

于 2018-06-05T08:07:04.943 回答
3

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

您可以简单地设置动画max-width(或max-height根据需要),0在移除和100%插入时设置动画。

于 2020-04-06T13:14:05.727 回答
0

我一直在尝试为我的 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/

于 2019-02-11T17:15:40.223 回答
0

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

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

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

以下是基于Maurici Abad使用 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>

于 2021-12-27T14:24:48.457 回答