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.
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.
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.
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.
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.
* { 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!
3.16.42.171