Instantly share code, notes, and snippets.
Created
November 4, 2015 02:58
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save thomas-jeepe/2e167b0b8713ec03e5cb to your computer and use it in GitHub Desktop.
Material design button with react-motion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { Component } from 'react'; | |
import ReactDom from 'react-dom' | |
import { TransitionMotion, spring, Motion } from 'react-motion'; | |
class RaisedButton extends Component { | |
constructor() { | |
super() | |
this.state = {mouse: [], now: 't' + 0, height: 0, width: 0} | |
} | |
handleClick({pageX, pageY}) { | |
let calcDiag = (a, b) => Math.sqrt((a * a) + (b * b)); | |
let style = {}; | |
const el = ReactDom.findDOMNode(this); | |
const elHeight = el.offsetHeight; | |
const elWidth = el.offsetWidth; | |
let rect = el.getBoundingClientRect(); | |
let offset = { | |
top: rect.top + document.body.scrollTop, | |
left: rect.left + document.body.scrollLeft, | |
}; | |
const pointerX = pageX - offset.left; | |
const pointerY = pageY - offset.top; | |
const topLeftDiag = calcDiag(pointerX, pointerY); | |
const topRightDiag = calcDiag(elWidth - pointerX, pointerY); | |
const botRightDiag = calcDiag(elWidth - pointerX, elHeight - pointerY); | |
const botLeftDiag = calcDiag(pointerX, elHeight - pointerY); | |
const rippleRadius = Math.max( | |
topLeftDiag, topRightDiag, botRightDiag, botLeftDiag | |
); | |
const rippleSize = rippleRadius * 2; | |
const left = pointerX - rippleRadius; | |
const top = pointerY - rippleRadius; | |
this.setState({ | |
mouse: [left, top], | |
now: 't' + Date.now(), | |
height: rippleSize, | |
width: rippleSize, | |
active: true | |
}) | |
} | |
handleMouseUp() { | |
this.setState(() => { | |
return { | |
mouse: [null, null], | |
active: false | |
}; | |
}); | |
} | |
handleMouseLeave() { | |
this.setState({hovered:false}) | |
} | |
handleMouseOver() { | |
this.setState({hovered:true}) | |
} | |
willLeave(key, valOfKey) { | |
return { | |
...valOfKey, | |
opacity: spring(0, [170, 20]) | |
}; | |
} | |
willEnter(key, valOfKey) { | |
return { | |
...valOfKey, | |
opacity: 0, | |
scale: 0, | |
}; | |
} | |
render() { | |
const {mouse: [mouseX, mouseY], now, height, width, active, hovered} = this.state | |
let { | |
label, | |
...other | |
} = this.props | |
const styles = mouseX == null ? {} : { | |
[now]: { | |
opacity: spring(.9, [200, 10]), | |
scale: spring(1, [140, 9]), | |
x: spring(mouseX), | |
y: spring(mouseY), | |
width, | |
height | |
} | |
} | |
return ( | |
<TransitionMotion | |
willEnter={this.willEnter} | |
willLeave={this.willLeave} | |
styles={styles}> | |
{circles => | |
<Motion style={{ | |
background: spring(hovered?.1566:0, [400,40]), | |
firstColor: spring(active?.124:.1566, [50,40]), | |
firstPixel: spring(active?3:1, [50,40]), | |
secondPixel: spring(active?10:6, [50,40]), | |
secondColor: spring(active?.227:.24, [50,40]), | |
thirdPixel: spring(active?3:1, [50,40]), | |
fourthPixel: spring(active?10:4, [50,40]) | |
}}> | |
{val => { | |
return <div | |
onMouseDown={::this.handleClick} | |
onMouseUp={::this.handleMouseUp} | |
onMouseLeave={::this.handleMouseLeave} | |
onMouseOver={::this.handleMouseOver} | |
{...other} | |
style={{ | |
backgroundColor: val.background > 0 ? `rgba(0,0,0,${val.background})`:'#ffffff', | |
boxShadow: `rgba(0,0,0,${val.firstColor}) 0px ${val.firstPixel}px ${val.secondPixel}px, rgba(0,0,0,${val.secondColor}) 0px ${val.thirdPixel}px ${val.fourthPixel}px`, | |
display: 'flex', | |
justifyContent: 'center', | |
alignItems: 'center', | |
position: 'absolute', | |
overflow: 'hidden', | |
width: 'auto', | |
minWidth: '64px', | |
height: 'auto', | |
minHeight: '36px', | |
borderRadius: '3px', | |
}}> | |
{Object.keys(circles).map(key => { | |
const { opacity, scale, x, y, width, height } = circles[key] | |
return ( | |
<div | |
key={key} | |
style={{ | |
opacity: opacity, | |
scale: scale, | |
width: width, | |
height: height, | |
left: x, | |
top: y, | |
transform: `scale(${scale})`, | |
WebkitTransform: `scale(${scale})`, | |
borderRadius: '100%', | |
position: 'absolute', | |
backgroundColor: 'rgba(0,0,0, 0.1234314)', | |
}} /> | |
) | |
})} | |
<span style={{userSelect: 'none', fontFamily: 'Roboto'}}>{this.props.label}</span> | |
</div> | |
} | |
} | |
</Motion> | |
} | |
</TransitionMotion> | |
); | |
} | |
} | |
class MainPage extends Component { | |
render() { | |
return <RaisedButton label='hi' onClick={v => console.log('called')} /> | |
} | |
} | |
export default MainPage | |
/* | |
It is still new, made it today so it is not nearly as refined as material-ui but | |
in the future it will have better support for react-native and a much smaller library size. | |
Although this is for a personal project, I might make it a library if someone wants to contribute | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment