Adam Olsson
Adam Olsson

Reputation: 32

Align single list item in center CSS

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.

The selected item appear in the center row

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

Answers (3)

Ori Drori
Ori Drori

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

haugsand
haugsand

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:

  • You have a list of four items.
  • Each item has width equal to 100px.
  • The total width of the list is therefore 400px.
  • The center point of the list is 200px from the left.
  • The center point of item number two is 150px from the left. We therefore have to move the list 200px - 150px = 50px from the left.
  • The center point of item number four is 350px from the left. We therefore have to move the list 200px - 350px = -150px from the left.

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

user7148391
user7148391

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

Related Questions