I'm trying to create an horizontally scrolling page navigation with animations using React and React.addons.CSSTransitionGroup. Currently I'm able to do the horizontal scrolling (with flexbox), page opening/closing, animating entering and leaving. But the animation is not exactly what I want.
Take a look at this example (jsfiddle).
When you click on a buttons on a previous page it currently pushes the pages that are leaving the screen to the right. Although the correct result would be to animate the leaving pages in the same place. I'm not sure how to achieve that effect with CSSTransitionGroup or know whether it is possible with it at all.
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: It's fine if it only works with the newest browsers.
This is a horizontal scrolling menu component for React. Menu component has adaptive width, just set width for parent container. Items width will be determined from CSS styles. For navigation, you can use scrollbar, native touch scroll, mouse wheel or drag by mouse.
To handle the onScroll event in React: Set the onScroll prop on an element or add an event listener on the window object. Provide an event handler function. Access relevant properties on the event or window objects.
React Transition Group is a good animation library and has a very small bundle size. It's one of the most popular animation libraries and should be considered for your next React project.
To get the vertical lineup of pages that you're looking for, we're going to have to change the HTML to add an extra layer. This extra layer will be a vertical channel that the article
s slide up and down in. Right now, new ones are just being pushed to the side because there is no parent layer to contain them.
Second problem is that all the HTML is created dynamically by React. Trying to go in and change a library/plugin is not a good idea, so I tried to create the effect you're going for with jQuery instead.
HTML Structure
First thing I did was sketch up a desired layout. We need three layers inside the overall parent:
Parent
Cell
Innercell (doubles in height to contain two articles)
Article
The dotted line in the sketch is the Innercell
, the Page rectangles are the Articles
CSS
For the most part, I'm using your original styling. The main difference is that in the extra layers between the cell
and article
. The cell
keeps it's height fixed at 100%
, then the innercell
gets height:200%
. It's also positioned absolutely, with bottom:0
. This is important, because it means the extra spacing is above the parent instead of below. We also make sure that the article itself is positioned absolutely, with bottom:0
. This places it in the bottom half of the innercell
.
JS
First thing to do here is to figure out the order we want things to happen. There are three possible routes we can go when a button is clicked.
If a rightmost button is clicked, we want to add a new cell, then slide in an article.
If a second-to-rightmost button is clicked, we just want to slide in a new article.
If any other button is clicked, we want to slide in a new article in the cell to the right, and all further right cells should slide down with no replacements.
Inside the the change article
circle, we need to:
1 Create new article
from template, insert all variable data, and place it in the empty space in the top of the innercell
.
2 We need to slide the innercell
down.
3 After the animation is done, we need to remove the previous article
and reset the 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>
Notes
I did end up adding a fourth layer, which I called innerarticle
. This was because you had an 8px
margin on your article
. With two article
s on top of each other vertically, the margin
collapses and doesn't stack. Be adding an innerarticle
and then giving the article
a padding:8px
, I could make sure it stacks.
Also, you may have noticed that I'm using a lot of custom element names. I do that because it works and it makes it easy for you to understand what's doing on. You can feel free to change them all to <div class="INSERTCURRENTELEMENT">
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With