Header logo
Free Consultation

Building a Slider with Flexbox

React Logo

A few months back we decided to rebuild Ashday.com using React. This was one of my first times getting into React and was perfect practice since there were a bunch of design elements that needed to be made into React components. Most of them were a simple translation of the way we were previously rendering them to a component, except in the cases where we were using jQuery. There is generally no need for jQuery in React, most things can be accomplished with Javascript written directly in the component.

When it came to React-ifying the rotating quote on our homepage, I decided to see if I could build a simple rotator using css flex and a little bit of Javascript. Naturally, I googled to see if I could find any flex based slider first. I didn’t really find much related to what I wanted to do, but flex still seemed like a good fit. I figured I could use the “nowrap” property to keep all the slides in a row and then use the order property on the individual slides to move them into view. Throw in some transitions, and it should work.

The basic HTML structure

Below is a simplified version of the markup from the quote slider on our homepage with quotes from our good buddy Joel. All of the top level components on our homepage are wrapped in a section tag with the ContentRow class. Next is a content wrapper that maintains width and padding for the feature. The next div is where I will define the flex properties and then the quote slide children are inside. 

<section class="ContentRow">
  <div class="Content">
    <div class="Quotes">
      <div class="Quote">
        <blockquote>
          <div>There are three crafts you need to look for in any web development firm: Coding chops, data engineering, and business analysis. Ashday is the only firm I've found with all 3... </div>
          <div class="attribution_one">Joel Hughes</div>
          <div class="attribution_two">SGC Inc.</div>
        </blockquote>
      </div>
      <div class="Quote">
        <blockquote>
          <div>...Their business analysis skills are so sharp that they will ensure that whatever solution they create will have lasting value and scaleability, even if that means asking the hard questions.</div>
          <div class="attribution_one">Joel Hughes</div>
          <div class="attribution_two">SGC Inc.</div>
        </blockquote>
      </div>
      <div class="Quote">
        <blockquote>
          <div>...We have saved tons of time, money, and frustration by working with Ashday over the years because they will not let you build something that won't correctly solve your business challenge...</div>
          <div class="attribution_one">Joel Hughes</div>
          <div class="attribution_two">SGC Inc.</div>
        </blockquote>
      </div>
    </div>
  </div>
</section>

Styling it all

This is where it starts to take shape. The Quotes div is going to be the parent container that flex positions the child quotes. This is a full page width feature, so it is set to 100% width, overflow is set to hidden so that the inactive quotes don’t have a chance of showing up. Finally it is set to nowrap so the quotes all stay inline.

The Quote div itself has a flex property of 0 0 100% set, which means that it won’t grow or shrink and it will take up 100% of the area. This keeps one quote in focus at a time. The base styling on Quote is for all of the inactive quotes. They are all absolutely positioned so that I can move them with the left property. The left: 100% property keeps them off the screen to the right, ready to move in toward left. All inactive slides have a flex order of 2. Finally, the transition allows them to gracefully appear and disappear. I also use opacity 0 to keep them hidden from view. The Quote that also has the “active” class overrides left, order and transition to make sure that it is the quote that is in the visible area. The Quote with the “last” class gets an order of 10 to ensure that it is the last quote (this number could get calculated and set using js) and its left is set to -100% to move it to the left. The current active quote will become the last quote. Order doesn’t actually matter in this example since everything is being moved around with absolute position but if you wanted to use this method to rotate items with more than one visible and no transition, order would be very helpful. I left it intact in case we wanted to add options to the way this component works in the future.

The quote itself is wrapped in a blockquote tag and I set a max-width to keep the text of the quote from stretching end to end of super large screens. Margin auto keeps it centered in the slide.

.Quotes {
  text-align: center;
  width: 100%;
  overflow: hidden;
  display: flex;
  flex-wrap: nowrap;
  .Quote {
    flex: 0 0 100%;
    position: absolute;
    width: 100%;
    left: 100%;
    order: 2;
    transition: left 1s, opacity 1s;
    opacity: 0;
    blockquote {
      max-width: 950px;
      margin: auto;
      position: relative;
    }
    &.active {
      left: 0;
      order: 1;
      transition: left 1s, opacity 1s;;
      opacity: 1;
    }
    &.last {
      left: -100%;
      order: 10;
      transition: left 1s, opacity 1s;;
     }
   }
 }

Javascript to finish it off

This part can be done with jQuery, but this example is React. Since I decided to move things around with absolute positioning, I couldn’t have the overall wrapper get the height from the largest slide. So the first step is to figure out the height of the largest slide and apply that to the parent wrapper.

Height for the slider is set in state with a call to a function in componentDidMount. I’m also tracking the width of the window, so I can recalculate everything on resize. The other thing going on here is that I’m checking to see if there is more than one quote and if there is, I start the rotation.

componentDidMount() {
    this.setState({width: window.innerWidth});
    if (this.props.quotes.length > 1) {
        window.setTimeout(this.rotateQuote, 10000);
    }
    this.getHeight(this.state.quotes);
    window.addEventListener("resize", 
this.updateDimensions.bind(this));
  }
---------------
getHeight = (quotes) => {
    let maxHeight = 0;
    quotes.map((quote, index) => {
        let quoteBlock = this.refs[quote.id].getBoundingClientRect();
        if (quoteBlock.height > maxHeight) {
            maxHeight = quoteBlock.height;
        }
        return false;
      });

      this.setState({barHeight: maxHeight});

};

The rotation itself is pretty simple. When it’s not paused, all that it does is increment the current quote in state on a timeout.

 rotateQuote = () => {
    if (!this.state.scrollPaused) {
      if (this.props.quotes.length === this.state.currQuote + 1) {
      this.setState({currQuote: 0});
    }
    else {
      let currQuote = this.state.currQuote;
      this.setState({currQuote: currQuote + 1});
    }
    window.setTimeout(this.rotateQuote, 10000);
  }
  else {
    window.setTimeout(this.rotateQuote, 10);
  }
};


Pausing of the slider is managed in state.
mouseEnter = () => {
this.setState({scrollPaused: true});
};

mouseLeave = () => {
this.setState({scrollPaused: false});
};

The render for the component looks like this. The “active” and “last” classes are set on each quote depending on the state, and that causes the thing to slide. Pausing is called onMouseEnter & onMouseLeave of the wrapper div.

 


render () {
    const quotes = this.props.quotes.map((quote, index) => {
      let stateClass = index === this.state.currQuote ? classes.active : index === this.state.currQuote - 1 || (this.state.currQuote === 0 && index === this.props.quotes.length - 1) ? classes.last : '';

    return (<div key={index} className={[classes.quote, stateClass].join(' ')} ref={quote.id}>
        <blockquote>
          <div className={classes.quoteBody}>{quote.quote}</div> {quote.attribution_1 ? <div className={classes.attribution_one}>{quote.attribution_1}</div> : ''}
          {quote.attribution_2 ? <div className={classes.attribution_two}>{quote.attribution_2}</div> : ''}
        </blockquote>
      </div>);
    });

    return (
      <div onMouseEnter={this.mouseEnter} onMouseLeave={this.mouseLeave} className={classes.Quotes} style=>
        {quotes}
      </div>
    )
  }

In the end, I’m not really moving the slider with the flex order property. There are a few things that made that difficult, one of them being that you can’t use a transition on the order property. However, if this slider was set up a little differently, you could in theory use order to move things along. With that said the use of flex for my little slider still saved me on a bunch of CSS and JavaScript and was a good fit for this lightweight use.

CTA

Author
Mike Goulding

Recent Posts

More Blogs