Reputation: 32
I am creating a navigation bar in Reactjs with four elements. These items are a simple unordered list with some css using flexbox to align them horizontaly.
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<ul/>
What I want to achieve is: When a list item is selected, align the selected list item to center. I have added a professional picture for clarification. This change will later be animated for a smooth transition, like a carousel.
Following is the css for <ul>
tag.
ul {
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
}
What I've tried is to use the align-self: center
on the one of the <li>
items, but with no luck.
Does anyone have any experience with doing something similar? I am open for all types of solutions, even those that does not use flexbox.
Thank You!
Upvotes: 0
Views: 2364
Reputation: 191976
Calculate the clicked <MenuItem>
center by using Element.getBoundingClientRect()
to get it's left
, and width
, and pass it to the parent (<Menu>
). In the parent use the <ul>
s ref to get it's left
and width with Element.getBoundingClientRect()
. Calculate the moveTo
state, and update the <ul>
s style transform: translateX()
accordingly:
const { Component } = React;
const items = ['One', 'Two', 'Three', 'Four'];
class MenuItem extends Component {
clickHandler = (e) => {
const { left, width } = e.target.getBoundingClientRect();
const itemCenter = left + width / 2;
this.props.updateCenter(itemCenter);
}
render() {
const { children } = this.props;
return (
<li onClick={this.clickHandler}>{children}</li>
);
}
}
class Menu extends Component {
state = {
moveTo: 0
};
updateCenter = (itemCenter) => {
const { left, width } = this.ul.getBoundingClientRect();
//this.ul.style.transform = `translateX(${center}px)`;
this.setState(() => ({
moveTo: left + width / 2 - itemCenter
}));
};
render() {
const { items } = this.props;
const { moveTo } = this.state;
return (
<nav>
<ul ref={(ul) => this.ul = ul} style={{
transform: `translateX(${moveTo}px)`
}}>
{
items.map((text) => (
<MenuItem key={text}
updateCenter={this.updateCenter}>
{text}
</MenuItem>
))
}
</ul>
</nav>
);
}
}
ReactDOM.render(
<Menu items={items} />,
demo
);
/** demo only - display the center **/
body::before {
position: absolute;
left: 50%;
height: 100vh;
border-right: 1px solid black;
content: '';
}
nav {
overflow: hidden;
}
ul {
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
transition: transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
li {
padding: 1em;
border: 1px solid black;
cursor: pointer;
}
ul li:not(:last-child) {
margin: 0 1em 0 0;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="demo"></div>
Upvotes: 1
Reputation: 420
Do the list elements have a fixed with, and do you know how many items there are? If so, you can calculate the center of the list, the item offset, and add a CSS transform.
Example:
If your list is dynamic, both regarding to list length and item width, you can use Element.getBoundingClientRect() to find the elements' dimensions, and use the same calculations as above.
Codepen: https://codepen.io/anon/pen/vjJMVL
HTML:
<ul class="selected-2">
<li>1</li>
<li class="selected">2</li>
<li>3</li>
<li>4</li>
</ul>
<ul class="selected-4">
<li>1</li>
<li>2</li>
<li>3</li>
<li class="selected">4</li>
</ul>
CSS:
ul {
display: flex;
justify-content: center;
list-style: none;
}
li {
width: 80px;
height: 80px;
background-color: #eee;
margin: 10px;
}
.selected {
background-color: #ccc;
}
.selected-2 {
transform: translateX(50px)
}
.selected-4 {
transform: translateX(-150px)
}
Upvotes: 2
Reputation:
Since i don't fully get what you want, this is the best i could have came up with, applying absolute positioning to the selected one and have it overlap the others with z-index
document.querySelectorAll('li').forEach(function(li){
li.addEventListener('click',function(e){
document.querySelectorAll('li').forEach(function(obj){
obj.classList.remove('selected');
});
e.target.classList.add("selected");
});
});
ul {
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
}
li{
border:1px solid;
padding:10px;
cursor:pointer;
position:relative;
transition:all ease 1s;
margin:0 20px;
}
.selected{
transform:scale(2.1);
background:white;
box-shadow:0px 0px 55px black;
position:absolute;
z-index:5;
}
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
</ul>
Upvotes: 0