Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="root"></div>
              
            
!

CSS

              
                html, body {
 width: 100%;
 height: 100%;
}

div, span {
 box-sizing: border-box;
}

div {
 padding: 0;
 margin: 0;
 text-align: center;
 position: relative;
 display:inline-block;
}

span {
 display: inline-block;
 position: relative;
 left:0;
 top:0;
 margin:0;
 padding:0;
 font-size: 10vh;
 text-align: center;
 color: rgba(230,230,235,0.5);
 text-shadow: 0 0 2px rgba(255,255,255,0.5);
}

p {
 font-size: 0.8rem;
 line-height: 1.2rem;
}

#root {
 display: flex;
 align-items: center;
 justify-content: center;
 width: 100%;
 min-height: 100vh;
 background: linear-gradient(to bottom, #eeeeee 0%,#cccccc 100%);
}

.holder {
}

.clock {
 overflow: hidden;
 border-radius: 16px; 
 border: 3px solid black;
 border-top-color: rgb(240,240,240);
 border-left-color: rgb(240,240,240);
 border-right-color: rgb(230,230,230);
 border-bottom-color: rgb(230,230,230);
 background: rgb(220,220,220);
 box-shadow: inset 0 0 18px rgba(50,50,50,0.7),
  5px 5px 20px hsl(0, 0%, 50%);
}

.unitContainer {
 margin: 0;
 padding: 18px;
}

.unitHolder {
 border: 3px solid rgb(100,100,100);
 border-right-color: rgb(200,200,200);
 border-bottom-color: rgb(240,240,240);
 background: linear-gradient(to bottom, 
  #4c4c4c 0%,
  #595959 12%,
  #666666 25%,
  #474747 39%,
  #2c2c2c 50%,
  #000000 51%,
  #111111 60%,
  #2b2b2b 76%,
  #1c1c1c 91%,
  #131313 100%); 
 border-radius: 8px; 
 position: relative;
 z-index: 0;
 overflow: hidden;
}

.unit {
 width: 2.1ch;
}

.crossout {
 text-decoration: line-through;
}

              
            
!

JS

              
                // Import the TransitionGroup from React's add-ons
const TransitionGroup = React.addons.TransitionGroup;

// Create a React component to hold the a generic unit of time. Hour, minute, second.
// It is here that we will control the unit's animation
class Unit extends React.Component {
 
 // Let's create a single simple method to handle the animation
 animate( el, callback, out ) {

  // Some constants to reduce repetition
  const dur = 0.5;
  const amount = 100;
  const ease = "Power1.easeInOut";
  
  // Check if it is the in animation or the out animation
  if( out ) {
   
   TweenMax.set( el, {
    position:"absolute"
   });
   
   TweenMax.to(el, dur, {
    yPercent:-amount,
    ease:ease,
    onComplete:callback
   });
   
  } else {

   TweenMax.from(this.el, dur, {
    yPercent:amount,
    ease:ease,
    onComplete:callback
   });

  }

 }
 
 // This method gets called every time the component is added to the DOM except on the first load
 componentWillEnter( callback ) {

  this.animate( this.el, callback );

 }
 
 // This method gets called every time the component is about to be removed from the DOM
 componentWillLeave( callback ) {

  this.animate( this.el, callback, true );

 }
 
 render() {

  return <span
          className="unit"
          ref={ ( el ) => { this.el = el; } } 
          >
           { this.props.number }
          </span> 

 }

}

// The clock component will be the one responsible for counting the time and tell each of the units of time what time they should display
class Clock extends React.Component {

 constructor( props ) {

  super( props );
   
  const date = new Date();
   
  this.visChange = this.visChange.bind(this);
   
  this.state = {
   hidden: "",
   hours: ("0" + date.getHours()).slice(-2),
   minutes: ("0" + date.getMinutes()).slice(-2),
   seconds: ("0" + date.getSeconds()).slice(-2)
  };

 }
 
  visChange() {

   if( document[this.state.hidden] ) {

    this.stopCount();

   } else {

    this.startCount();

   }

  }
 
  startCount() {

   this.timerID = setInterval( () => this.tick(), 1000 );

  }
 
  stopCount() {

   clearInterval( this.timerID );

  }

  componentDidMount() {

   // from: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API
   // Set the name of the hidden property and the change event for visibility
   let visibilityChange;

   if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support 

    this.setState( { hidden:"hidden" } );
    visibilityChange = "visibilitychange";

   } else if ( typeof document.msHidden !== "undefined" ) {

    this.setState( { hidden:"msHidden" } );
    visibilityChange = "msvisibilitychange";

   } else if ( typeof document.webkitHidden !== "undefined" ) {

    this.setState( { hidden:"webkitHidden" } );
    visibilityChange = "webkitvisibilitychange";

   }
   
   this.startCount();

   document.addEventListener( visibilityChange, this.visChange );

  }

  componentWillUnmount() {

   this.stopCount();

  }

  tick() {

   let date = new Date();
   let hours = ("0" + date.getHours()).slice(-2);
   let minutes = ("0" + date.getMinutes()).slice(-2);
   let seconds = ("0" + date.getSeconds()).slice(-2);
   
   this.setState({
    hours: hours,
    minutes: minutes,
    seconds: seconds
   });

  }

  render() {

   // Here's the gotcha that took me a long while to understand.
   // It is not enough to simply update the 'number' props of each <Unit /> component. We need to create a new component at each render and use it to replace the old one.
   // We give each unit a unique key based on the time and add a string to differenciate between the siblings so that React can correctly identify which component is which.
   const hours = <Unit key={"h" + this.state.hours} number={this.state.hours} />
   const mins = <Unit key={"m"+ this.state.minutes} number={this.state.minutes} />
   const secs = <Unit key={"s" + this.state.seconds} number={this.state.seconds} />

    // React requires us to return one single root element. And because of the animation effect we are trying to achieve here, we have to encapsulate each of our <Unit /> components on a <TransitionGroup />

    return (
     <div className="holder">
       <h1>React Clock</h1>
       <div className="clock">
        <div className="unitContainer">
         <TransitionGroup component="div" className="unitHolder">
          {hours}
         </TransitionGroup>
        </div>
        <div className="unitContainer">
         <TransitionGroup component="div" className="unitHolder">
          {mins}
         </TransitionGroup>
        </div>
        <div className="unitContainer">
         <TransitionGroup component="div" className="unitHolder">
          {secs}
         </TransitionGroup></div>
       </div>
       <p className="crossout">There is a tiny wobble when viewing in chrome. If you know why that happens, let me know. I haven't managed to squash that bug.</p>
      <p className="crossout">Erm... By just adding this text, the wobble in Chrome goes away. Go figure.</p>
      <p className="crossout">Nope. Adding the extra line brought the wobble back but sideways. Now with this paragraph. Wobble is back to up and down.</p>
       <p>While in safari, the colour of the number changes when animating. One day, I'll investigate the reason why...</p>
      <p>18/05/2018: Changing the display of the #root element to 'flex' sorted out the Chrome wobble... Go figure. Must have been something to do with the transform I was applying before to center everything in both axis</p>
      </div>
    );
  }
}

ReactDOM.render(<Clock />, document.getElementById('root'));

/*
 Other Notes:
 
 This would not work because of the ':' present inside the transition group 
 <TransitionGroup>
  {hours}:{mins}:{secs}
 </TransitionGroup>
 
 However, this would
 <TransitionGroup>
  {hours}{mins}{secs}
 </TransitionGroup>
  
 This would also work
 <TransitionGroup>
  {hours}<span>:</span>{mins}<span>:</span>{secs}
 </TransitionGroup>
  
 This would not work because the encompassing <div> is not a React component and therefore does not communicates with the <TransitionGroup>
 <TransitionGroup>
  <div>{hours}<span>:</span>{mins}<span>:</span>{secs}</div>
 </TransitionGroup>



 Here, we are only updating values, we are not adding or removing the components, which would not trigger the lifecycle hooks
 <TransitionGroup>
  <Unit key="hours" number={this.state.hours} />
  <Unit key="minutes" number={this.state.minutes} />
  <Unit key="seconds" number={this.state.seconds} />
 </TransitionGroup>
*/

              
            
!
999px

Console