Using the React-Motion animation library

React-Motion is a very nice physics based animation library created by Cheng Lou, an avid contributor to many things React. In some ways, React-Motion is similar to jQuery animate. Specifically, it's a fancy interface to tween numbers. In previous examples, animation was achieved by strategically changing CSS classes attached to elements. When classes change CSS properties, which are the target of a transition property, the browser automatically uses the declared transition property details to handle animation frame-by-frame. Another, more direct, way to animate is to change CSS declarations on the style attribute of the virtual DOM elements themselves.

How React-Motion works

Animation on the web is a process of interpolating intermediate values of properties, such as position, over the course of some time between starting and ending values. To calculate these in-between values, or to tween them using a library, typically involves specifying the start and end values, the duration over which you want the animation to take place, and the easing function or a name for the way the intermediate values are calculated. React-Motion does this tweening calculation a bit differently than using easing function by offering a very basic and flexible interface. The interface is called a spring, and it uses stiffness and damping values to calculate the intermediate steps. It also integrates nicely into the React rendering process and is JSX friendly.

There's one quick item to note if you read the GitHub documentation for React-Motion. The documentation says that the Motion component parameters defaultStyle and style should have the same shape. This is referring to data structures. React-Motion springs are very flexible, allowing these values to be objects, arrays, or objects containing arrays or other objects. Just be sure that both parameters, defaultStyle and style, are the same shape or structure so that the library knows how to correlate the numbers being tweened or interpolated. This allows React-Motion to tween many properties at once.

For this example we are using version 0.3.1 of React-Motion.

Clock animation

This animation example uses React-Motion to animate the hands of a clock. The clock is a set of overlapping canvas elements. The clock is continuously animating and the time can be set using input fields for hours, minutes, and seconds.

Note

The source code of the file clockanimation.zip can be found at http://bit.ly/Mastering-React-10-clockanim-gist and a live example can be found at http://j.mp/Mastering-React-10-clockanim-fiddle.

Here are some static shots of the clock animation demo in action.

Clock animation

JavaScript source

var Motion = ReactMotion.Motion
,   spring = ReactMotion.spring
;

Motion is the simplest React-Motion animation component and the one that we'll use here. There's also a spring function, which handles easing functions via a simple interface using physics semantics of a spring: stiffness and damping.

var Clock = React.createClass({
  getInitialState: function () {
    return { baseDate: new Date(), hours: 0, mins: 0, secs: 0 };
  },

As the time counts forward, it does so from a base date. This is initially the time when the component is instantiated but will be replaced if props for hours, minutes, and seconds are passed in. More on this is discussed later when the componentWillReceiveProps method is explained. The initial values for hours, minutes, and seconds are also set in that method. These values will be in degrees for the rotation of the clock hands.

  componentDidMount: function () {
    var node      = ReactDOM.findDOMNode(this)
    ,   get       = node.querySelector.bind(node)
    ,   parts     = node.querySelectorAll('canvas')
    ,   faceCtx   = get('.clockface').getContext('2d')
    ,   hourCtx   = get('.hourhand').getContext('2d')
    ,   minCtx    = get('.minutehand').getContext('2d')
    ,   secondCtx = get('.secondhand').getContext('2d')
    ,   width     = node.clientWidth
    ,   height    = node.clientHeight
    ;

Here we are just getting references to our canvas elements and their canvas drawing context in order to set a baseline clock drawing to 12:00:00. We are about to draw into the canvas elements, so this is done in componentDidMount after the canvas elements are first placed into the DOM.

    Array.prototype.forEach.call(parts, function (canvas) {
      canvas.setAttribute('width', width);
      canvas.setAttribute('height', height);
    });

Set all of the canvas dimensions equal to the container dimensions.

    // render off pixel boundaries for bolder lines
    faceCtx.translate(.5, .5);

This is a common trick to make a canvas draw appropriately bold lines. If you draw on the lines of the pixel grid, then the adjacent pixels will get anti-aliased to a lighter color. Think of it like pouring a finite amount of water onto the ridge of an ice cube tray. Each adjacent tray cell would get about half of the water. This context translation aligns strokes to the middle of the cell/pixel.

    // create the clock face
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].forEach(
    function (mult) {
      faceCtx.save();
        faceCtx.translate(width>>1,height>>1);
        faceCtx.rotate((360 / 12 * mult)  * (Math.PI / 180));
        faceCtx.translate(0, -(width>>1));
        faceCtx.beginPath();
        // longer ticks every 3 hours
        faceCtx.moveTo(0, (mult)%3 ? 8 : 15); 
        faceCtx.lineTo(0, 1);
        faceCtx.stroke();
        faceCtx.restore();
    });

This code draws the tick marks for the hours on the clock face. It does this by drawing one tick, then rotating the context and repeating the draw commands. A longer tick is drawn every three hours as is typical on round clock faces.

    faceCtx.beginPath();
    faceCtx.arc(width>>1, height>>1, (width>>1)-1 , 0, 2 * Math.PI, false);
    faceCtx.stroke();

This part draws the clock face circle using the arc method. You may notice a somewhat cryptic trick here that was also in the previous code chunk. The double greater-than symbol is a bit shift to the right. This is a divide by two, but don't do it unless you know your operand is divisible by two or you'll lose precision.

    // create the clock hands
    hourCtx.translate(width>>1, width>>1); // center
    hourCtx.lineWidth = 5;
    hourCtx.moveTo(0,0);
    hourCtx.lineTo(0, -(width>>1) +18);
    hourCtx.stroke();
    
    minCtx.translate(width>>1, width>>1);
    minCtx.strokeStyle = "#666";
    minCtx.lineWidth = 2;
    minCtx.moveTo(0,0);
    minCtx.lineTo(0, -(width>>1) + 10);
    minCtx.stroke();
    
    secondCtx.translate(width>>1, width>>1);
    secondCtx.strokeStyle = "#f00"; // red second hand
    secondCtx.lineWidth = 2;
    secondCtx.moveTo(0,12);
    secondCtx.lineTo(0, -(width>>1) + 2);
    secondCtx.stroke();

The preceding code draws the hands of our clock into each respective canvas.

    // start with initial state base date
    this.setBaseDate(this.state.baseDate);
    // begin aggressively calculating updates
    window.requestAnimationFrame(this.tick);
  },

This kicks off time calculation. The call to setBaseDate will initialize our starting point for time measurement. Then requestAnimationFrame begins collating time passage.

  componentWillReceiveProps: function (nextProps) {
    var newBaseDate = new Date();
    // if props aren't parseable set date to current
    if (!isNaN(parseInt(nextProps.hours,10) + parseInt(nextProps.mins,10) + parseInt(nextProps.secs,10))) {
        newBaseDate.setHours(nextProps.hours%24);
        newBaseDate.setMinutes(nextProps.mins%60);
        newBaseDate.setSeconds(nextProps.secs%60);
      this.setBaseDate(newBaseDate);
    } else {
      this.setBaseDate(new Date());
    }
  },

The componentWillReceiveProps lifecycle event is the interface used to set time from outside the component. There are props for hours (hours), minutes (mins), and seconds (secs). If the props are changed, they are checked for validity and a new base date is established. If they change and aren't valid, then we reset base date to the current time.

  setBaseDate: function (date) {
    this.setState({ baseDate: date });
    this.startTick = new Date(); // re-establish starting point
  },
  format: function (num) {
    return num > 9 ? num : '0'+num;
  },

This format function is used during render to display the numeric form of the time. It prepends a zero to the time component if it's a single digit.

  tick: function () {
    var nextTick = new Date()
    ,   diff = nextTick.valueOf() - this.startTick.valueOf()
    ;
    
    // Here we use a logical OR to clamp 
    // the values to whole numbers
    // This allows us to render just once per second 
    // while aggressively updating our time data
    var clockState = {
        hoursDisp: ((this.state.baseDate.getHours()+diff/1000/3600)|0),
        minsDisp: ((this.state.baseDate.getMinutes()+diff/1000/60)|0),
        secsDisp: ((this.state.baseDate.getSeconds()+diff/1000)|0),
    };
    clockState.amPm = clockState.hoursDisp%24 > 12 ? 'pm':'am';
    
    // degrees
    clockState.hours = clockState.hoursDisp*30;
    clockState.mins = clockState.minsDisp*6;
    clockState.secs = clockState.secsDisp*6;
    
    this.setState(clockState);
    // resume updates at 60fps
    window.requestAnimationFrame(this.tick); 
  },

The tick function is probably the most interesting part of this demo aside from the usage of React-Motion. Every time tick is called, it calculates how much time has passed since our base date (startTick). Invoking valueOf on a Date object in JavaScript returns the UTC milliseconds, or time since the Unix Epoch (January 1, 1970 00:00:00). If you are curious about why it's that date, search for unix time or epoch time. There's a storied past which includes technical reasoning for that date and time. For our purposes, it gives a common point from which we can calculate our time difference.

After the time difference from the base date is calculated, some state is prepared and set. There's another trick here. Each of the clockState components has |0 after it. This logical or truncates a floating-point number in JavaScript to the whole part of the number (without rounding) very efficiently. This is important for only triggering a render when appropriate, as you will soon see.

Notice that requestAnimationFrame is used to calculate the time passage continuously. Browsers attempt to render at 60 frames per second. When they re-render the page they attempt to reconcile DOM changes and navigate the specificity of CSS in order to finally lay out the actual pixels onto the screen. This can be an expensive process and, if you change the DOM with abandon, it can cause a lot of recalculation thrash. Think of requestAnimationFrame as a means to say, "hey browser, next time you decide to re-calc and re-render, please run this function first".

This means that our clock calculation will run at roughly 60 frames per second or every 16.667 milliseconds. That's pretty fast, and we don't want the React render pipeline to run that fast. This is what the next lifecycle method is for.

  // only allow render when there's a value change
  shouldComponentUpdate: function (nextProps, nextState) {
    return (
      nextState.hours !== this.state.hours || 
      nextState.mins !== this.state.mins ||
      nextState.secs !== this.state.secs
    );
  },

The shouldComponentUpdate method is how we can aggressively track the time with requestAnimationFrame, but only render the component when a full hour, minute, or second changes. This is why earlier the values were truncated with a logical or, so that this lifecycle method only returns true every second or so. This means that the clock time will remain accurate. It will not accidentally skip a second because of a browser hiccup, but it will also not render inefficiently. However, we don't want the clock hands to move instantaneously between whole number values. This is where React-Motion finally comes into the picture. It will handle the tweening of the clock hands between the one-second renders of the greater clock component.

  render: function () {
    return (
      <div className="clock-component">
        <canvas ref="clockface" className="clockface"></canvas>
        <Motion style={{
          hours: spring(this.state.hours),
          mins: spring(this.state.mins),
          secs: spring(this.state.secs)
        }}>

The next animation endpoint for our Motion component is an object, style, and is managed using the spring function. The spring function calculates the interpolated values using an easing function implied by the stiffness and damping configuration. Here, we let spring use the defaults for those values. If we wanted to we could pass a second parameter (array) to each spring invocation where the first array value was the stiffness and the second was the damping.

        {({hours,mins,secs}) =>
          <div className="hands">
            <canvas ref="hourhand" className="hourhand" style={{
              WebkitTransform: `rotate(${hours}deg)`,
              transform: `rotate(${hours}deg)`
            }}></canvas>
            <canvas ref="minutehand" className="minutehand" style={{
              WebkitTransform: `rotate(${mins}deg)`,
              transform: `rotate(${mins}deg)`
            }}></canvas>
            <canvas ref="secondhand" className="secondhand" style={{
              WebkitTransform: `rotate(${secs}deg)`,
              transform: `rotate(${secs}deg)`
            }}></canvas>
          </div>
        }
        </Motion>

These components within the Motion component will be rapidly rendered as a result of the React-Motion spring tweening operation. The current value for each internal frame calculation will be available in the hours, mins, and secs parameters. As we did before with the list animation example height, the interpolated values are placed directly into the style attribute of each respective virtual DOM clock hand canvas element.

        <pre className="digital">
          {this.format(this.state.hoursDisp%12)}:{this.format(this.state.minsDisp%60)}:{this.format(this.state.secsDisp%60)} {this.state.amPm}
        </pre>
      </div>
    );
  }
});

The preceding code is just a small digital display to go below our animated clock. It will only render once a second as allowed by shouldComponentUpdate.

var ClockExample = React.createClass({
  getInitialState: function () { return {}; },
  getVal: function (name) {
    return ReactDOM.findDOMNode(this.refs[name]).value;
  },
  setTime: function () {
    this.setState({
      hours: this.getVal('hours'),
      mins: this.getVal('mins'),
      secs: this.getVal('secs')
    });
  },

The wrapper interface component, ClockExample, keeps its own state for hours, minutes, and seconds, and manages them with interactive inputs and a set button. The set button calls the setTime handler here and sets the state for each time component.

  render: function () {
    return (
      <div className="clock-example">
        <fieldset>
          <legend>Set the time</legend>
          <label>hours   <input maxLength="2" ref="hours" /></label>
          <label>minutes <input maxLength="2" ref="mins" /></label>
          <label>seconds <input maxLength="2" ref="secs" /></label>
          <button onClick={this.setTime}>SET</button>
        </fieldset>
        <Clock hours={this.state.hours} mins={this.state.mins} secs={this.state.secs}/>
      </div>
    );
  }
});

When setTime is called, the values from the inputs are set on the local state of this outer component, triggering a render. When this component renders, the state for hours, minutes, and seconds is passed to our animated clock via props.

ReactDOM.render(<ClockExample />, document.getElementById('app'));

Don't forget to render the top-level component.

CSS source

* {
  box-sizing: border-box;
}

.clock-example {
  display: -webkit-flex; /*safari*/
  display: flex;
  align-items: flex-start;
  width: 300px;
  justify-content: space-between;
}

Flexbox is a wonderful layout tool. It is used here to put the clock controls and the clock component side by side.

.clock-example fieldset {
  width: 150px;
}
.clock-example input {
  display: block;
  line-height: 18px;
  border: 1px solid #aaa;
  border-radius: 4px;
  width: 100%;
}
.clock-example button {
  border: none;
  color: white;
  background-color: #446688;
  margin: 10px 0;
  padding: 10px 20px;
  cursor: pointer;
  outline: none;
  box-shadow: 1px 1px 2px #aaa;
}
.clock-example button:active {
  transform: translateY(2px);
  transition: transform .1s ease;
  box-shadow: 0 0 2px #aaa;
}

The preceding styles are visual treatment for the input form used to set the time into the clock component.

.clock-component {
  position: relative;
  width: 100px;
  height: 100px;
  margin: 20px;
}
.clock-component canvas {
  position: absolute;
  left: 0;
  top: 0;
}
.clock-component .digital {
  position: absolute;
  bottom: -35px;
  width: 100%;
  text-align: center;
}:

The actual clock component styles place all the canvas components (clock hands) on top of one another so they overlap correctly. The small digital display is positioned under the animated clock. Notice that, unlike the popover and menu animation examples, there aren't any animation styles for the clock component. The React-Motion component and spring handles all of that!

CSS source
CSS source
CSS source
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.42.171