<div id="page"></div>
@import "compass";
@import "susy";
/*===============================
=            GENERAL            =
===============================*/

/*----------  FONTS  ----------*/
@import url(https://fonts.googleapis.com/css?family=Roboto:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic,900,900italic&subset=latin,greek);
@import url(https://fonts.googleapis.com/css?family=Roboto+Mono:400,100,100italic,300,300italic,400italic,500,500italic,700,700italic);

/*----------  VARIABLES  ----------*/
$text_color: #1f2e3e;
$blue: #2196f3;
$orange: #ffa500;
$green: #8bc34a;

html,
body {
	margin: 0;
	padding: 0;
}

body {
	font-family: 'Roboto', sans-serif;
	font-size: 100%;
	color: $text_color;
  position: absolute;
  width: 100%;
  height: 100%;
}

#page {
	position: relative;
	width: 100%;
	height: 100%;
	@include display-flex;
	@include justify-content( center );
	@include align-items( center );
	background-color: $blue;

	&.orange {
		background-color: $orange;
	}

	.material_button {
		position: relative;
		cursor: pointer;
		width: 64px;
		height: 64px;
		line-height: 64px;
		text-align: center;

		&:before {
			top: 0;
			left: 0;
			width: 100%;
			height: 100%;
			content: "";
			@include border-radius( 50% );
			position: absolute;
			@include box-shadow(  0 5px 11px 0 rgba(0, 0, 0, 0.18), 0 4px 15px 0 rgba(0, 0, 0, 0.15) );
			opacity: 0;
			@include transition( opacity 0.25s ease-out );
		}

		&:hover {
			&:before {
				opacity: 1;
			}
		}

		i {
			position: relative;
			height: 64px;
			width: 64px;
			line-height: 64px;
			position: relative;
			z-index: 99;

			&.done {
				opacity: 0;
				visibility: hidden;
				position: absolute;
				width: 100%;
				height: 100%;
				top: 0;
				left: 0;
			}
		}

		svg {
			position: absolute;
			top: 50%;
			left: 50%;
			@include transform( translate3d(-50%,-50%,0) );
			z-index: 0;
			overflow: visible;

			circle {
				fill: #fff;

				&#green_ripple {
					fill: $green;
				}
			}
		}

		.progress {
			font-family: 'Roboto Mono', sans-serif;
			opacity: 0;
			visibility: hidden;
			position: absolute;
			width: 100%;
			height: 100%;
			text-align: center;
			z-index: 9;
		}
	}
}


footer {
  position: absolute;
  width: 100%;
  left:0;
  bottom:0;
  text-align:center;
  padding: 16px 0;
  color: #FFFFFF;
  font-size: 14px;
  a {
    color: #1e59af;
  }
}
View Compiled
/*=================================
=            FUNCTIONS            =
=================================*/
// window size function
function wndsize() {
	var w = 0;
	var h = 0;
	//IE
	if (!window.innerWidth) {
		if (!(document.documentElement.clientWidth == 0)) {
			//strict mode
			w = document.documentElement.clientWidth;
			h = document.documentElement.clientHeight;
		} else {
			//quirks mode
			w = document.body.clientWidth;
			h = document.body.clientHeight;
		}
	} else {
		//w3c
		w = window.innerWidth;
		h = window.innerHeight;
	}
	return {
		width: w,
		height: h
	};
}

// map function
Number.prototype.map = function ( in_min , in_max , out_min , out_max ) {
	return ( this - in_min ) * ( out_max - out_min ) / ( in_max - in_min ) + out_min;
}

/*=================================
=            VARIABLES            =
=================================*/
var doc = document,
	page = doc.getElementById('page'),
	timeAnim = 1.25;


/*========================================
=            REACT COMPONENTS            =
========================================*/


var Ripple = React.createClass({
	getInitialState: function() {
		return {
			x:"",
			y: "",
			w: wndsize().width,
			h: wndsize().height
		}
	},
	rippleAnim: function(event) {

		var dom 			= this.refs.ripple.getDOMNode(),
			greenDom 		= this.refs.greenripple.getDOMNode(),
			tl 				= new TimelineMax(),
			offsetX			= Math.abs( (this.state.w / 2) - event.pageX ),
			offsetY			= Math.abs( (this.state.h / 2) - event.pageY ),
			deltaX			= (this.state.w / 2) + offsetX,
			deltaY			= (this.state.h / 2) + offsetY,
			scale_ratio		= Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));

		TweenMax.set([dom, greenDom], {transformOrigin: "center center"});

		tl
		.to(dom, timeAnim, {
			attr: {
				r: scale_ratio
			},
			ease: Power3.easeOut,
			onComplete: function() {
				classie.add(page, "orange");
			}
		})
		.to(dom, 2*timeAnim, {
			attr: {
				r: 32
			},
			delay: timeAnim/3,
			ease: Power0.easeNone
		})
		.to(greenDom, timeAnim/2, {
			attr: {
				r: scale_ratio
			},
			delay: timeAnim/3,
			ease: Power3.easeOut
		});
	},
	componentWillReceiveProps: function(nextProps) {
		if (nextProps.activity === "play") {

			switch(nextProps.point) {
				case "one":
					this.setState({
						x: nextProps.event.pageX,
						y: nextProps.event.pageY
					});
					this.rippleAnim(nextProps.event);
					break;
				case "two":
					var dom 			= this.refs.ripple.getDOMNode(),
						greenDom 		= this.refs.greenripple.getDOMNode(),
						tl 				= new TimelineMax(),
						offsetX			= Math.abs( (this.state.w / 2) - this.state.x ),
						offsetY			= Math.abs( (this.state.h / 2) - this.state.y ),
						deltaX			= (this.state.w / 2) + offsetX,
						deltaY			= (this.state.h / 2) + offsetY,
						scale_ratio		= Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));

					tl
					.to(dom, timeAnim, {
						attr: {
							r: scale_ratio
						},
						onComplete: function() {
							classie.remove(page, "orange");
							TweenMax.set(greenDom, {
								attr: {
									r: 32
								}
							});
						},
						ease: Power3.easeOut
					})
					.to(dom, timeAnim/2, {
						attr: {
							r: 32
						},
						ease: Power3.easeOut
					});
					break;
			}
		}
	},
	render: function() {
		return (
			<svg height="1" width="1">
				<circle ref="greenripple" id="green_ripple" cx="0" cy="0" r="32" />
				<circle ref="ripple" id="white_ripple" cx="0" cy="0" r="32" />
			</svg>
		);
	}

});


var Button = React.createClass({
	handleClick: function(e) {
		var self = this;
		if (this.state.action === "paused") {
			this.setState({
				action: "play",
				point: "one",
				progress: 0,
				event: e.nativeEvent
			});
			var arrow 		= this.refs.arrow_icon.getDOMNode(),
				done 		= this.refs.done_icon.getDOMNode(),
				progress 	= this.refs.progress.getDOMNode(),
				tl 			= new TimelineMax();

			tl.fromTo(arrow, timeAnim, {
				yPercent: 0,
				autoAlpha: 1,
				scale: 1
			},{
				yPercent: 20,
				autoAlpha: 0,
				//delay: timeAnim/3,
				ease: Power3.easeOut
			})
			.fromTo(progress, 2*timeAnim/3, {
				yPercent: -20,
				autoAlpha: 0,
				scale: 0.6
			},{
				yPercent: 0,
				autoAlpha: 1,
				scale: 1,
				ease: Power3.easeOut
			}, "-="+timeAnim/3)
			.to(self.state, 2*timeAnim, {
				progress: 100,
				ease: Power0.easeNone,
				onUpdate: function(tween) {
					self.setState({
						progress: parseInt(tween.target.progress),
						action: "paused"
					})
				},
				onUpdateParams:["{self}"]
			})
			.to(progress, timeAnim/4, {
				yPercent: 20,
				autoAlpha: 0,
				scale: 0.6,
				delay: timeAnim/3,
				ease: Power3.easeOut
			})
			.fromTo(done, timeAnim/4, {
				yPercent: -20,
				autoAlpha: 0,
				scale: 0.6
			},{
				yPercent: 0,
				autoAlpha: 1,
				scale: 1,
				ease: Power3.easeOut
			})
			.to(done, 2*timeAnim/3, {
				yPercent: 20,
				autoAlpha: 0,
				scale: 0.6,
				delay: timeAnim/3,
				onStart: function() {
					self.setState({
						action: "play",
						point: "two",
						progress: 0,
						event: ""
					});
				},
				ease: Power3.easeOut
			})
			.fromTo(arrow, 2*timeAnim/3, {
				yPercent: -20,
				scale: 0.6,
				autoAlpha: 0
			},{
				yPercent: 0,
				scale: 1,
				autoAlpha: 1,
				delay: timeAnim/2,
				ease: Power3.easeOut,
				onComplete: function() {
					self.setState({
						action: "paused",
						point: "one",
						progress: 0,
						event: ""
					});
				}
			});
		}
	},
	getInitialState: function() {
		return {
			action: "paused",
			point:"",
			progress: 0,
			event: ""
		}
	},
	render: function() {
		return (
			<div className="material_button" onClick={this.handleClick}>
				<i ref="done_icon" className="material-icons done">done</i>
				<div ref="progress" className="progress">{this.state.progress}</div>
				<i ref="arrow_icon" className="material-icons">arrow_downward</i>
				<Ripple activity={this.state.action} event={this.state.event} point={this.state.point} />
			</div>
		);
	}

});

React.render(
	<Button />,
	page
);
View Compiled

External CSS

  1. https://fonts.googleapis.com/icon?family=Material+Icons

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/classie/1.0.1/classie.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.2/TweenMax.min.js