使用水平滚动处理 React 动画
Posted
技术标签:
【中文标题】使用水平滚动处理 React 动画【英文标题】:Handling React animation with horizontal scrolling 【发布时间】:2015-03-09 09:19:14 【问题描述】:我正在尝试使用 React 和 React.addons.CSSTransitionGroup 创建一个带有动画的水平滚动页面导航。目前我能够进行水平滚动(使用 flexbox)、页面打开/关闭、动画进入和离开。但动画并不是我想要的。
看看this example (jsfiddle)。
当您单击前一页上的按钮时,它当前会将离开屏幕的页面推到右侧。尽管正确的结果是在同一位置为离开页面设置动画。我不确定如何使用 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:如果它只适用于最新的浏览器就可以了。
【问题讨论】:
您考虑过使用绝对定位吗?如果没有,您必须确保视口的高度等于在一列中包含两行元素的最大空间量。 是的,我已经考虑过了。我想在 CSS 中保留尽可能多的样式。 你一定要使用 React 吗?此外,当用户快速点击时,期望的行为是什么?是否应该在动画完成之前忽略所有额外的点击,还是应该排队? @AndyM - 我不必使用 React,但它确实解决了我更新屏幕的很多问题,而且我已经有很多依赖它的代码。当用户快速点击时,动画和页面应该重叠,最新的屏幕是最顶部的项目。基本上,用户可以操作屏幕的最长时间是进入和离开屏幕所需的时间。 你能举一个动画应该是什么样子的例子吗?我可能会想出一些东西,但不确定你希望它是什么样子。 【参考方案1】:问题
要获得您正在寻找的页面的垂直排列,我们将不得不更改 html 以添加一个额外的层。这个额外的层将是一个垂直通道,article
s 在其中上下滑动。现在,新的只是被推到一边,因为没有父层来包含它们。
第二个问题是所有的 HTML 都是由 React 动态创建的。尝试进入并更改库/插件不是一个好主意,因此我尝试使用 jQuery 创建您想要的效果。
可能的解决方案
HTML 结构
我做的第一件事就是勾勒出想要的布局。我们需要在整个父级内部三层:
Parent
Cell
Innercell (doubles in height to contain two articles)
Article
草图中的虚线是Innercell
,页面矩形是Articles
CSS
在大多数情况下,我使用的是您的原始样式。主要区别在于cell
和article
之间的额外层。 cell
将其高度固定为100%
,然后innercell
得到height:200%
。它也是绝对定位的,bottom:0
。这很重要,因为这意味着额外的间距在父级上方而不是下方。我们还确保文章本身是绝对定位的,bottom:0
。这会将其置于innercell
的下半部分。
JS
在这里要做的第一件事是弄清楚我们希望事情发生的顺序。单击按钮时,我们可以走三种可能的路线。
如果点击了最右边的按钮,我们想添加一个新单元格,然后滑入一篇文章。
如果点击了倒数第二个按钮,我们只想滑入一篇新文章。
如果单击任何其他按钮,我们希望在右侧单元格中滑入一篇新文章,并且所有右侧单元格都应该向下滑动且没有替换。
在change article
圈内,我们需要:
1 从模板新建article
,插入所有变量数据,放在innercell
顶部的空白处。
2 我们需要向下滑动innercell
。
3 动画完成后,我们需要去掉之前的article
,重新设置innercell
最终代码
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;
);
/* 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%;
<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>
备注
我最终添加了第四层,我称之为innerarticle
。这是因为您的article
上有一个8px
边距。两个article
s 垂直重叠,margin
折叠并且不堆叠。添加一个innerarticle
,然后给article
一个padding:8px
,我可以确保它堆叠起来。
另外,您可能已经注意到我使用了很多自定义元素名称。我这样做是因为它有效,而且它让你很容易理解正在做什么。您可以随意将它们全部更改为<div class="INSERTCURRENTELEMENT">
【讨论】:
很好的答案,很好的解释!结果非常顺利。 +1 这个非常好的答案。 不错的答案。但是,用 vanilla JS 做这件事很容易,我在将那部分与 React 集成时遇到了麻烦。基本上我不太确定如何与 React 系统交互。 (同样快速点击也无济于事,即想象如果用户误点击按钮,他将不得不等到页面出现,同时用户的一些输入也可能丢失。) 我的评论回复是“我不必使用 React”。如果您仍在尝试,那么显然这个答案的 JS 部分不起作用。 HTML/CSS 部分确实如此,所以唯一的问题是,你能让 React 输出一个额外的层吗? 我接着说“......但它确实解决了我更新屏幕的很多问题,而且我已经有很多依赖它的代码。”,这意味着解决了 N 个不同的问题.我猜 React 可以有额外的层,但我还没有完全弄清楚里面是如何连接的。我猜有一些很好的方法可以在 div 进入时从左侧固定位置,让它在就位时弯曲,然后在它离开时固定左侧位置......但我不知道该怎么做正确。 我想我们可以做到!所以现在,您正在为页面创建一个类。我们只需要为调用页面的单元格创建一个新类。棘手的部分在于让按钮移动页面而不是单元格。【参考方案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/
玩得开心
【讨论】:
这种方法似乎有很多小故障。尤其是当您快速点击项目时。 嗯,这是一个javascript问题,首先你需要停止按下一个按钮,告诉第一个按钮移动完成,其次当你添加一个新部分时,它需要在添加之前有css类,而不是添加后。这就是造成您看到的故障的原因。以上是关于使用水平滚动处理 React 动画的主要内容,如果未能解决你的问题,请参考以下文章