material-ui
Version:
Material Design UI components built with React
196 lines (167 loc) • 4.61 kB
JSX
let React = require('react');
let StylePropable = require('../mixins/style-propable');
let Dom = require('../utils/dom');
let RippleCircle = require('./circle-ripple');
let TouchRipple = React.createClass({
mixins: [StylePropable],
propTypes: {
centerRipple: React.PropTypes.bool,
color: React.PropTypes.string,
opacity: React.PropTypes.number,
},
getInitialState() {
return {
ripples: [{
key: 0,
started: false,
ending: false,
}],
};
},
render() {
let styles = this.mergeAndPrefix({
height: '100%',
width: '100%',
position: 'absolute',
top: 0,
left: 0,
overflow: 'hidden',
}, this.props.style);
return (
<div
onMouseUp={this._handleMouseUp}
onMouseDown={this._handleMouseDown}
onMouseOut={this._handleMouseOut}
onTouchStart={this._handleTouchStart}
onTouchEnd={this._handleTouchEnd}>
<div style={styles}>
{this._getRippleElements()}
</div>
{this.props.children}
</div>
);
},
start(e, isRippleTouchGenerated) {
let ripples = this.state.ripples;
let nextKey = ripples[ripples.length-1].key + 1;
let style = !this.props.centerRipple ? this._getRippleStyle(e) : {};
let ripple;
//Do nothing if we're starting a click-event-generated ripple
//while having touch-generated ripples
if (!isRippleTouchGenerated) {
for (let i = 0; i < ripples.length; i++) {
if (ripples[i].touchGenerated) return;
}
}
//Start the next unstarted ripple
for (let i = 0; i < ripples.length; i++) {
ripple = ripples[i];
if (!ripple.started) {
ripple.started = true;
ripple.touchGenerated = isRippleTouchGenerated;
ripple.style = style;
break;
}
}
//Add an unstarted ripple at the end
ripples.push({
key: nextKey,
started: false,
ending: false,
});
//Re-render
this.setState({
ripples: ripples,
});
},
end() {
let ripples = this.state.ripples;
let ripple;
let endingRipple;
//End the the next un-ended ripple
for (let i = 0; i < ripples.length; i++) {
ripple = ripples[i];
if (ripple.started && !ripple.ending) {
ripple.ending = true;
endingRipple = ripple;
break;
}
}
//Only update if a ripple was found
if (endingRipple) {
//Re-render
this.setState({
ripples: ripples,
});
//Wait 2 seconds and remove the ripple from DOM
setTimeout(() => {
ripples.shift();
if (this.isMounted()) {
this.setState({
ripples: ripples,
});
}
}, 2000);
}
},
_handleMouseDown(e) {
//only listen to left clicks
if (e.button === 0) this.start(e, false);
},
_handleMouseUp() {
this.end();
},
_handleMouseOut() {
this.end();
},
_handleTouchStart(e) {
this.start(e, true);
},
_handleTouchEnd() {
this.end();
},
_getRippleStyle(e) {
let style = {};
let el = React.findDOMNode(this);
let elHeight = el.offsetHeight;
let elWidth = el.offsetWidth;
let offset = Dom.offset(el);
let isTouchEvent = e.touches && e.touches.length;
let pageX = isTouchEvent ? e.touches[0].pageX : e.pageX;
let pageY = isTouchEvent ? e.touches[0].pageY : e.pageY;
let pointerX = pageX - offset.left;
let pointerY = pageY - offset.top;
let topLeftDiag = this._calcDiag(pointerX, pointerY);
let topRightDiag = this._calcDiag(elWidth - pointerX, pointerY);
let botRightDiag = this._calcDiag(elWidth - pointerX, elHeight - pointerY);
let botLeftDiag = this._calcDiag(pointerX, elHeight - pointerY);
let rippleRadius = Math.max(
topLeftDiag, topRightDiag, botRightDiag, botLeftDiag
);
let rippleSize = rippleRadius * 2;
let left = pointerX - rippleRadius;
let top = pointerY - rippleRadius;
style.height = rippleSize + 'px';
style.width = rippleSize + 'px';
style.top = top + 'px';
style.left = left + 'px';
return style;
},
_calcDiag(a, b) {
return Math.sqrt((a * a) + (b * b));
},
_getRippleElements() {
return this.state.ripples.map((ripple) => {
return (
<RippleCircle
key={ripple.key}
started={ripple.started}
ending={ripple.ending}
style={ripple.style}
color={this.props.color}
opacity={this.props.opacity} />
);
});
},
});
module.exports = TouchRipple;