React horizontal scroll animation processing

I am trying to create a horizontal scroll of page navigation with animations using React and React.addons.CSSTransitionGroup. Currently, I can do horizontal scrolling (using flexbox), opening / closing a page, animating input and leaving. But the animation is not quite what I want.

Take a look at this example ( jsfiddle ).

When you click on the buttons on the previous page, it currently pushes the pages that exit the screen to the right. Although the correct result would be to animate the remaining pages in the same place. I am not sure how to achieve this effect using CSSTransitionGroup or to find out if this 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: This is normal if it only works with the latest browsers.

+5
source share
2 answers

Problem

To get a vertical list of the pages you are looking for, we will need to modify the HTML to add an extra layer. This extra layer will be a vertical channel in which the article slide up and down. Right now, new ones are simply pasted aside because there is no parent layer to place them.

The second problem is that all HTML is created dynamically using React. Trying to enter and modify the library / plugin is not a good idea, so I tried to create the effect that you are going to use with jQuery.

Possible Solution

HTML structure

The first thing I did was sketch the desired layout. We need three layers inside the common parent:

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

The dashed line in the sketch is Innercell , the page rectangles are Articles

enter image description here

CSS

For the most part, I use your original style. The main difference is that in additional layers between cell and article . cell captures the height at 100% , then Innercell gets height:200% . It is also positioned absolutely, with bottom:0 . This is important because it means that the extra interval is higher than the parent, not lower. We also make sure that the article itself is positioned absolutely, from bottom:0 . This puts him in the lower half of Innercell .

Js

The first thing to do is figure out the order in which we want things to happen. There are three possible routes by which you can click the button.

  • If the right-most button is pressed, we want to add a new cell, and then move to the article.

  • If the "second to right" button is pressed, we just want to go to the new article.

  • If you clicked any other button, we want to go to the new article in the cell on the right, and all the other right cells should move down without any replacements.

enter image description here

Inside the circle of change article we need:

1 Create a new article from the template, insert all the data in the variable and place it in the empty space at the top of Innercell .

2 We need to push Innercell down.

3 After the animation is complete, we need to delete the previous article and reset Innercell

Final code

 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

In the end, I added a fourth layer, which I called innerarticle . This happened because your article had an 8px field. When two article stacked vertically on top of each other, the margin shrinks and does not stack. Add innerarticle and then giving article a padding:8px , I could make sure it is packed.

In addition, you may have noticed that I use a lot of custom element names. I do this because it works, and itโ€™s easy for you to understand what needs to be done. You can freely change them all to <div class="INSERTCURRENTELEMENT">

+9
source

I delved into this and came up with an easy solution by adding this to 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; } 

Working example: http://jsfiddle.net/6985qr33/5/

Good luck

+2
source

Source: https://habr.com/ru/post/1210854/


All Articles