Rotareti
Rotareti

Reputation: 53763

Variable transitions in React using ReactCSSTransitionGroup

ReactCSSTransitionGroup uses a CSS file to define and store transitions. That is nice and works well. But what if a component needs a variable transition configuration e.g a variable transition-duration that takes any value from 0ms to 10000ms? How would I change the value of transition-duration from within the component's Javascript class, when it is stored in a CSS file?

Example:

Let's say you want to make a slideshow component and you want to implement an option to change the fading-duration (the time the animation takes to fade from one slide to another). You call the component like this:

<Slideshow fadeDuration={1000}>

This is how the component adds the transitions to the slide element that is going to be rendered:

render: function() {
  return (
    <div>
      <ReactCSSTransitionGroup
        transitionName="example"
        transitionEnterTimeout={500}
        transitionLeaveTimeout={300}>
        {this.slide}
      </ReactCSSTransitionGroup>
    </div>
  );
}

This is the css file needed by ReactCSSTransitionGroup to define the transitions:

.example-enter {
  opacity: 0.01;
}

.example-enter.example-enter-active {
  opacity: 1;
  transition: opacity 500ms ease-in;
}

.example-leave {
  opacity: 1;
}

.example-leave.example-leave-active {
  opacity: 0.01;
  transition: opacity 300ms ease-in;
}

Now how do I use props.fadeDuration inside the component to change the value of transition-duration which lays inside the css file?

So far the only thing that came to my mind was, that I have to remove the ReactCSSTransitionGroup and create the transitions manually using refs and the 'Style Object' 'transition' property. Is this the way to go? Or how would I do it?

Upvotes: 1

Views: 1478

Answers (1)

Alexandr Lazarev
Alexandr Lazarev

Reputation: 12862

You can use jss library to manipulate css from javascript. It's a very good library and solves many cases. Take a look.

UPDATED

I'll show you how to use it. In major cases components using jss-react look like:

import React, { Component } from 'react'
import useSheet from 'react-jss'

// You can use jss directly too!
import jss from 'jss'
import vendorPrefixer from 'jss-vendor-prefixer'
jss.use(vendorPrefixer())

const styles = {
  button: {
    'background-color': 'yellow'
  },
  label: {
    'font-weight': 'bold'
  }
}

class Button extends Component {
  render() {
    const { classes } = this.props.sheet

    return (
      <div className={classes.button}>
        <span className={classes.label}>
          {this.props.children}
        </span>
      </div>
    )
  }
}

export default useSheet(Button, styles)

Styles are defined outside the component, and then a higher-order component is used to wrap it and inject the styles. In major cases it's ok, but since we need to add css rules from inside of the component we need to use another approach. We need to use jss as a child element instead of higher order component. That will permit us to add rules from the components with the help of addRules() method.

import React, { Component } from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import useSheet from 'react-jss'
import jss from 'jss'

let styles = {};
const sheet = jss.createStyleSheet(styles, {named: false})
class Foo extends Component {
  constructor(props) {
    super(props);
    this.state ={
      isVisible: false
    }
  }
  toggle() {
    this.setState({
      isVisible: !this.state.isVisible
    })
  }

  render() {
    const duration = 500
    const { classes } = sheet
    sheet.addRules(
      {            
        '.example-enter': {
          opacity: 0.01
        },
        '.example-enter-active': {
          color: 'red',
          opacity: 1,
          transition: 'opacity ' + duration + 'ms ease-in'
        },
        '.example-leave': {
          opacity: 1
        },
        '.example-leave-active': {
          opacity: 0.01,
          transition: 'opacity '+ duration +'ms ease-in'
        }
      }
    );
    return (
      <div className={classes.red}>
        <Jss sheet={sheet} />
        <a onClick={this.toggle.bind(this)}>Click</a>
          <ReactCSSTransitionGroup
          transitionName="example"
          transitionEnterTimeout={10}
          transitionLeaveTimeout={600}>
          { this.state.isVisible ? <div>Visible</div> : null}
        </ReactCSSTransitionGroup>
      </div>
  );
  }
}


const map = new WeakMap()

class Jss extends Component {
  componentWillMount() {
    const {sheet} = this.props
    const counter = map.get(sheet) || 0
    if (!counter) sheet.attach()
    map.set(sheet, counter + 1)
  }

  componentWillUnmount() {
    const {sheet} = this.props
    const counter = map.get(sheet) - 1
    if (counter) {
      map.set(sheet, counter)
    } else {
      sheet.detach()
      map.delete(sheet)
    }
  }

  render() {
    return null
  }
}

export default Foo 

One thing to keep in mind. By default, jss adds some suffixes to the classes in order to get rid of global selectors and isolate them. A class looks like: .example-enter-441163035. It's very comfortable to use this approach if we set className attributes manually, like: <div className={classes.button}>. So, or you have to set transition classes manually, like this:

<ReactCSSTransitionGroup
  transitionName={{
    enter: classes.example-enter,
    enterActive: classes.example-enter-active,
    leave: classes.example-leave,
    leaveActive: classes.example-leave-active.
  }}
  transitionEnterTimeout={10}
  transitionLeaveTimeout={600}>

Or you have to use global selectors by setting named option as false when initiating sheet (like I did in my example):

const sheet = jss.createStyleSheet(styles, {named: false})

Hope it helps! Nevertheless, take a look at this library. It's really cool and promotes a very interesting approach to css.

Upvotes: 3

Related Questions