使用水平滚动处理React动画

时间:2015-01-11 14:00:52

标签: css animation reactjs flexbox

我正在尝试使用React和React.addons.CSSTransitionGroup创建一个带有动画的水平滚动页面导航。目前我可以进行水平滚动(使用flexbox),页面打开/关闭,动画进入和离开。但动画并不是我想要的。

查看this examplejsfiddle)。

当您单击上一页上的按钮时,它当前会将离开屏幕的页面向右推。虽然正确的结果是在同一个地方动画离开页面。我不确定如何使用CSSTransitionGroup实现这种效果,或者知道它是否可以使用它。

function generateId() {
  var r = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (var i = 0; i < 10; i += 1) {
    r += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return r;
}

var PageList = {
  pages: [],
  listener: function(newpages) {},
  open: function(caption, index) {
    if (index != null) {
      this.pages = this.pages.slice(0, index + 1);
    }
    this.pages.push({
      id: generateId(),
      caption: caption,
      width: (150 + Math.random() * 50) | 0
    });
    this.listener(this.pages);
  }
};
PageList.open("Main");

var PageLink = React.createClass({
  render: function() {
    var self = this;
    return React.DOM.button({
      className: "pagelink",
      onClick: function() {
        PageList.open(self.props.caption, self.props.pageIndex);
      }
    }, self.props.caption);
  }
});

var Page = React.createClass({
  render: function() {
    return React.DOM.article({
        className: "page",
        style: {
          width: this.props.page.width + "px"
        }
      },
      React.DOM.h1({}, this.props.page.caption),
      React.createElement(PageLink, {
        caption: "Alpha",
        pageIndex: this.props.index
      }),
      React.createElement(PageLink, {
        caption: "Beta",
        pageIndex: this.props.index
      }),
      React.createElement(PageLink, {
        caption: "Gamma",
        pageIndex: this.props.index
      })
    );
  }
});

var Pages = React.createClass({
  componentDidMount: function() {
    var self = this;
    PageList.listener = function(pages) {
      self.setState({
        pages: pages
      });
    };
  },
  getInitialState: function() {
    return {
      pages: PageList.pages
    };
  },
  render: function() {
    var pages = this.state.pages.map(function(page, index) {
      return React.createElement(Page, {
        key: page.id,
        index: index,
        page: page
      });
    });
    return React.createElement(
      React.addons.CSSTransitionGroup, {
        component: "section",
        className: "pages",
        transitionName: "fall"
      }, pages);
  }
});

React.initializeTouchEvents(true);
React.render(
  React.createElement(Pages),
  document.getElementById("main")
);
/* animation */

.fall-enter {
  transform: translate(0, -100%);
  transform: translate3d(0, -100%, 0);
}
.fall-enter.fall-enter-active {
  transform: translate(0, 0);
  transform: translate3d(0, 0, 0);
  transition: transform 1s ease-out;
}
.fall-leave {
  index: -1;
  transform: translate(0, 0);
  transform: translate3d(0, 0, 0);
  transition: transform 1s ease-in;
}
.fall-leave.fall-leave-active {
  transform: translate(0, 100%);
  transform: translate3d(0, 100%, 0);
}
/* other */

* {
  box-sizing: border-box;
}
.pagelink {
  padding: 5px;
  margin: 5px 0px;
  width: 100%;
}
.pages {
  display: flex;
  flex-flow: row;
  overflow-x: scroll;
  overflow-y: hidden;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #ddd;
}
.pages:after {
  flex: none;
  -webkit-flex: none;
  display: block;
  content: " ";
  width: 100px;
}
.page {
  flex: none;
  -webkit-flex: none;
  margin: 8px;
  padding: 10px 31px;
  background: #fff;
  overflow: auto;
  box-shadow: 2px 1px 4px rgba(0, 0, 0, 0.2);
  border-radius: 3px;
}
<body id="main">
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.12.2/react-with-addons.js"></script>

PS:如果它只适用于最新的浏览器,那就没关系了。

2 个答案:

答案 0 :(得分:9)

问题

要获得您正在寻找的页面的垂直阵容,我们将不得不更改HTML以添加额外的图层。这个额外的图层将是article向上和向下滑动的垂直通道。现在,新的图层被推到一边,因为没有父图层来包含它们。

第二个问题是所有HTML都是由React动态创建的。尝试进入并更改库/插件并不是一个好主意,所以我尝试用jQuery创建你想要的效果。

可能的解决方案

HTML结构

我做的第一件事就是草拟出所需的布局。我们在整体父母中需要三层:

Parent
    Cell
        Innercell (doubles in height to contain two articles)
            Article

草图中的虚线是Innercell,Page矩形是Articles

enter image description here

<强> CSS

在大多数情况下,我使用您的原始样式。主要区别在于cellarticle之间的额外层。 cell将其高度固定为100%,然后innercell获得height:200%。它也绝对定位于bottom:0。这很重要,因为它意味着额外的间距高于父母而不是下面。我们还确保文章本身绝对定位,bottom:0。这将它放在innercell的下半部分。

<强> JS

这里要做的第一件事就是弄清楚我们希望事情发生的顺序。单击按钮时,我们可以使用三种可能的路径。

  1. 如果点击最右边的按钮,我们想要添加一个新单元格,然后在文章中滑动。

  2. 如果点击第二个到最右边的按钮,我们只想在新文章中滑动。

  3. 如果单击任何其他按钮,我们希望在单元格中向右滑动新文章,并且所有其他右侧单元格应向下滑动而不进行替换。

  4. enter image description here

    change article圈内,我们需要:

    1从模板中创建新的article,插入所有可变数据,并将其放在innercell顶部的空白处。

    2我们需要向下滑动innercell

    3动画完成后,我们需要删除之前的article并重置innercell

    最终守则

    &#13;
    &#13;
    var cellTemplate = '<cell data-col="DATACOL"><innercell><article></article></innercell></cell>';
    var innerTemplate = '<article class="new"><innerarticle><h1>TITLE</h1><button>Alpha</button><button>Beta</button><button>Gamma</button></innerarticle></article>';
    var numCells = 2;
    var animating = 0;
    
    $('body').on('click', 'article button', function(event){
        if (!animating) {
            var currentCell = parseInt($(this).parent().parent().parent().parent().attr('data-col'));
            var title = $(this).text();
            if (currentCell == numCells) {
                addCell(title);
            } else if (currentCell == numCells - 1) {
                changeArticle(title,numCells);
            } else {
                removeExtraCells(title,currentCell);
            }
        }
    });
    
    function removeExtraCells(title,currentCell) {
        var tempCurrentCell = currentCell;
        changeArticle(title,tempCurrentCell+1);
        while (tempCurrentCell < numCells) {
            tempCurrentCell++;
            deleteArticle(title,tempCurrentCell+1);
        }
        numCells = currentCell+1;
    }
    
    function addCell(title){
        numCells++
        var html = cellTemplate.replace('DATACOL',numCells);
        $('section').append(html);
        changeArticle(title,numCells);
    }
    
    function changeArticle(title,changingCol) {
        var cell = $('cell[data-col="'+changingCol+'"] innercell');
        var html = innerTemplate.replace('TITLE', title).replace('DATACOL', numCells - 1);
        cell.prepend(html);
        triggerAnimation(cell);
    }
    
    function deleteArticle(title,changingCol) {
        var cell = $('cell[data-col="'+changingCol+'"] innercell');
        var html = '<article></article>';
        cell.prepend(html).addClass('deleting');
        triggerAnimation(cell);
    }
    
    function triggerAnimation(cell) {
        cell.addClass('animating');
        window.setTimeout(function(event){
            cell.css('bottom','-100%');
            animating = 1;
        },50);
    }
    
    $('body').on('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', 'innercell', function(event){
        if ( $(this).hasClass('deleting') ) {
            $(this).parent().remove();
        } else {
            $(this).children('article:last-child').remove();
            $(this).children('article.new').removeClass('new');
            $(this).removeClass('animating');
            $(this).css('bottom','0');
        }
        animating = 0;
    });
    &#13;
    /* other */
     * {
        box-sizing: border-box;
    }
    
    section {
        display: flex;
        flex-flow:row;
        overflow-x: scroll;
        overflow-y: hidden;
        position: fixed;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background: #ddd;
    }
    
    cell {
        flex: none;
        -webkit-flex: none;
        display:block;
        height:100%;
        float:left;
        position:relative;
        width:166px;
    }
    innercell {
        display:block;
        height:200%;
        width:100%;
        position:absolute;
        left:0;
        bottom:0;
    }
    .animating {
        transition: bottom 1s ease-out;
    }
    article {
        display:block;
        padding:8px;
        height:50%;
        position:absolute;
        left:0;
        bottom:0;
    }
    article.new {
        bottom:auto;
        top:0;
    }
    innerarticle {
        display:block;
        padding: 10px 31px;
        background: #fff;
        overflow: auto;
        box-shadow: 2px 1px 4px rgba(0, 0, 0, 0.2);
        border-radius: 3px;
        height:100%;
    }
    
    innerarticle button {
        padding: 5px;
        margin: 5px 0px;
        width: 100%;
    }
    &#13;
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <section>
        <cell data-col="1">
            <innercell>
                <article>
                    <innerarticle>
                        <h1>Main</h1>
                        <button>Alpha</button>
                        <button>Beta</button>
                        <button>Gamma</button>
                    </innerarticle>
                </article>
            </innercell>
        </cell>
        <cell data-col="2">
            <innercell>
                <article>
                    <innerarticle>
                        <h1>Alpha</h1>
                        <button>Alpha</button>
                        <button>Beta</button>
                        <button>Gamma</button>
                    </innerarticle>
                </article>
            </innercell>
        </cell>
    </section>
    &#13;
    &#13;
    &#13;

    备注

    我最终添加了第四层,我称之为innerarticle。这是因为您的8pxarticle个保证金。当两个article在垂直方向上彼此叠加时,margin会折叠并且不会堆叠。要添加innerarticle,然后给article padding:8px,我可以确保它堆叠。

    另外,您可能已经注意到我使用了很多自定义元素名称。我之所以这样做,是因为它有效,让您轻松了解自己在做什么。您可以随意将它们全部更改为<div class="INSERTCURRENTELEMENT">

答案 1 :(得分:2)

我深入研究了这一点,并通过将其添加到css中来提出一个简单的解决方案:

.fall-enter.fall-enter-active {
    position: absolute;
    height: 97%;
    margin-top: -5px;
}
.fall-enter.fall-enter-active, .fall-leave.fall-leave-active {
    transition: transform 1s ease-in;
}

工作示例: http://jsfiddle.net/6985qr33/5/

玩得开心