Kapost Engineering

Recent Posts


Recent Comments


Archives


Categories


Meta


Horizontal React Component Slider

Justin HundleyJustin Hundley

If you have ever had issues with making a horizontal menu responsive, you might want to try our “react-component-slider” component.  It’s a very straightforward way to deal with overflowing, horizontally-oriented components without having to do any tricky resizing or CSS media queries.  This JavaScript solution will automatically add navigation arrows to scroll left and right when content is overflowing its container.  You can find the package on NPM and GitHub:

https://github.com/kapost/react-component-slider
https://www.npmjs.com/package/@kapost/react-component-slider

 

How It Works:

The <ComponentSlider/> component sets references to both the “container” and the “contents” of the slider.  The container is simply a wrapper placed around the children with 100% width.  With this setup, we can always measure the width of the container relative to the size of the window and also measure the width of the child elements, which will be a static value.

If the width of the children ever exceeds the width of the container, we know that our “content” is overflowing, and we can place arrows as necessary.  When a navigation arrow is clicked, we can calculate the overflowing width of the content, and set a left margin to shift the content over.

Diving into the code…

We start with a left margin of 0, meaning the slider is scrolled all the way to the left:

this.state = {
  marginLeft: 0,
};

In this state, when the left margin is 0, we do not need to show the left arrow:

renderLeftArrow = () => {
  if (this.state.marginLeft !== 0) {
    return (
      <button className="caret caret-left" onClick={this.handleLeftClicked}>
        {this.props.renderLeftArrow()}
      </button>
    );
  }
  return null;
}

For the rendering of the right arrow, we use the following calculation:

const remainingWidth = contentWidth - (sliderWidth - arrowWidth) - currentMarginLeft;

 
This takes the static width of the “content” referred to earlier and subtracts the current slider width (the visible width), the width taken by the arrow itself, and the current left margin (if non zero that means that the slider is currently tabbed over to the right).  If the result is greater than 0, we know that something is overflowing, meaning we should display a right navigation arrow.

Now we just have to figure out what to set the left margin when each arrow is clicked.

When clicking the left arrow…

handleLeftClicked = () => {
  const currentMarginLeft = this.state.marginLeft;
  const sliderWidth = this.slider.offsetWidth;
  let marginLeft;
  if (currentMarginLeft > sliderWidth) {
    marginLeft = currentMarginLeft - sliderWidth;
  } else {
    marginLeft = 0;
  }
  this.setState({ marginLeft });
}

If currently we are tabbed over in the slider to the right an amount less than or equal to the width of the slider container, we can simply slide all the way back the left (margin left of 0).  If it’s greater than the width of the slider container, that means we can’t slide left far enough to show all of the remaining content that is hidden to the left of the slider.  So we just subtract as much as we can from the left margin (the width of the slider container) to slide the viewing window to the left.

When clicking the right arrow…
 

handleRightClicked = () => {
  const currentMarginLeft = this.state.marginLeft;
  const sliderWidth = this.slider.offsetWidth
  const contentWidth = this.sliderContent.offsetWidth;
  const remainingWidth = contentWidth - (sliderWidth - arrowWidth) - currentMarginLeft;
  let marginLeft;

  if (remainingWidth > 0) {
    if (remainingWidth <= sliderWidth) {
      marginLeft = currentMarginLeft + remainingWidth;
    } else {
      marginLeft = currentMarginLeft + sliderWidth;
    }
  } else {
    marginLeft = currentMarginLeft;
  }
  this.setState({ marginLeft });
}

 

If the amount of width left to display is less than or equal to the width of the slider, we can shift left enough to show the entire remaining width since that means there is enough viewing room to show the remaining content.  Otherwise, we can shift left for how much viewing room we do have (the width of the slider container).

Also, when the component mounts, we are adding listeners for when the window is resized:

componentDidMount() {
 window.addEventListener('resize', this.handleResize());
 this.resetMargin();
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize());
}

This is useful to reset the slider so that arrows can be shown if the content starts overflowing as a result of the resize, or if the content is no longer overflowing we should no longer show the arrows.

Demo

Here is a React sandbox if you want to play around with the component:

That’s it!  If you have a suggestion to improve this component, or encounter any issues, please feel free to open an issue on the GitHub repo. Thanks!

 

full stack engineer at kapost, with an emphasis on front end development. drop me a line: justin@kapost.com

Comments 0
There are currently no comments.