Merge pull request #146 from react-toolbox/boost-ripple-performance

Boost ripple performance
old
Javi Velasco 2015-11-21 12:59:33 +01:00
commit 80dfc65815
13 changed files with 60 additions and 56 deletions

View File

@ -17,6 +17,7 @@
"parser": "babel-eslint", "parser": "babel-eslint",
"plugins": [ "plugins": [
"babel",
"react" "react"
], ],
@ -168,6 +169,7 @@
"operator-linebreak": [2, "after"], "operator-linebreak": [2, "after"],
"padded-blocks": [0], "padded-blocks": [0],
"prefer-const": [2], "prefer-const": [2],
"prefer-spread": [2],
"quote-props": [0], "quote-props": [0],
"radix": [0], "radix": [0],
"semi": [2], "semi": [2],
@ -210,6 +212,7 @@
}], }],
"react/jsx-uses-react": [2], "react/jsx-uses-react": [2],
"react/jsx-uses-vars": [2], "react/jsx-uses-vars": [2],
"react/react-in-jsx-scope": [2] "react/react-in-jsx-scope": [2],
"babel/object-shorthand": [2]
} }
} }

View File

@ -1,4 +1,4 @@
//-- Typography // scss-lint:disable VendorPrefix
@mixin typo-preferred-font($use-preferred: true) { @mixin typo-preferred-font($use-preferred: true) {
@if $use-preferred { @if $use-preferred {
font-family: $preferred-font; font-family: $preferred-font;
@ -262,21 +262,18 @@
@mixin ripple-loading($name, $width, $height, $opacity: 0.3) { @mixin ripple-loading($name, $width, $height, $opacity: 0.3) {
@keyframes #{$name} { @keyframes #{$name} {
0% { 0% {
width: 0;
height: 0;
opacity: $opacity; opacity: $opacity;
transform: translate3d(-50%, -50%, 0) scale(0);
} }
95% { 95% {
width: $width;
height: $height;
opacity: 0; opacity: 0;
transform: translate3d(-50%, -50%, 0) scale(1);
} }
100% { 100% {
width: 0;
height: 0;
opacity: 0; opacity: 0;
transform: translate3d(-50%, -50%, 0) scale(0);
} }
} }
} }

View File

@ -51,8 +51,9 @@ class Button extends React.Component {
}; };
render () { render () {
const {accent, flat, floating, href, icon, label, loading, mini, const {accent, flat, floating, href, icon, label,
primary, raised, ripple, toggle, tooltip, tooltipDelay, ...others} = this.props; loading, mini, primary, raised, ripple, toggle,
tooltip, tooltipDelay, ...others} = this.props; //eslint-disable-line no-redeclare
const element = href ? 'a' : 'button'; const element = href ? 'a' : 'button';
const level = primary ? 'primary' : accent ? 'accent' : 'neutral'; const level = primary ? 'primary' : accent ? 'accent' : 'neutral';
const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : toggle ? 'toggle' : 'flat'; const shape = flat ? 'flat' : raised ? 'raised' : floating ? 'floating' : toggle ? 'toggle' : 'flat';

View File

@ -3,6 +3,7 @@
@import "./config"; @import "./config";
%button { %button {
@include typo-button();
position: relative; position: relative;
display: inline-block; display: inline-block;
height: $button-height; height: $button-height;
@ -24,7 +25,6 @@
border: 0; border: 0;
} }
> span:not(.tooltip) { > span:not(.tooltip) {
@include typo-button();
display: inline-block; display: inline-block;
line-height: $button-height; line-height: $button-height;
vertical-align: middle; vertical-align: middle;
@ -41,7 +41,7 @@
pointer-events: none; pointer-events: none;
} }
[data-react-toolbox="ripple"] { [data-react-toolbox="ripple"] {
overflow: hidden; @include rounded-overflow();
} }
} }
@ -97,10 +97,8 @@
} }
.icon { .icon {
font-size: 120%; font-size: 120%;
vertical-align: middle;
}
.icon {
line-height: $button-height; line-height: $button-height;
vertical-align: middle;
} }
[data-react-toolbox="ripple"] { [data-react-toolbox="ripple"] {
border-radius: 50%; border-radius: 50%;

View File

@ -1,13 +1,9 @@
@import "./base"; @import "./base";
@import "~normalize.css";
$import-normalize: true !default;
$import-font: true !default; $import-font: true !default;
$import-flex-attributes: false !default; $import-flex-attributes: false !default;
@if $import-normalize == true {
@import "~normalize.css";
}
@if $import-font == true { @if $import-font == true {
@import url($font-roboto-url); @import url($font-roboto-url);
} }

View File

@ -28,7 +28,7 @@ class Dropdown extends React.Component {
const client = event.target.getBoundingClientRect(); const client = event.target.getBoundingClientRect();
const screen_height = window.innerHeight || document.documentElement.offsetHeight; const screen_height = window.innerHeight || document.documentElement.offsetHeight;
const up = this.props.auto ? client.top > ((screen_height / 2) + client.height) : false; const up = this.props.auto ? client.top > ((screen_height / 2) + client.height) : false;
this.setState({active: true, up: up}); this.setState({active: true, up});
}; };
handleSelect = (item) => { handleSelect = (item) => {

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import prefixer from '../utils/prefixer';
import style from './style'; import style from './style';
class Ripple extends React.Component { class Ripple extends React.Component {
@ -25,32 +26,41 @@ class Ripple extends React.Component {
width: null width: null
}; };
handleEnd = (touch = false) => { handleEnd = () => {
document.removeEventListener(touch ? 'touchend' : 'mouseup', this.handleEnd); document.removeEventListener(this.touch ? 'touchend' : 'mouseup', this.handleEnd);
this.setState({active: false}); this.setState({active: false});
}; };
start = ({ pageX, pageY }, touch = false) => { start = ({pageX, pageY}, touch = false) => {
document.addEventListener(touch ? 'touchend' : 'mouseup', this.handleEnd.bind(this, touch)); this.touch = touch;
document.addEventListener(this.touch ? 'touchend' : 'mouseup', this.handleEnd);
const {top, left, width} = this._getDescriptor(pageX, pageY); const {top, left, width} = this._getDescriptor(pageX, pageY);
this.setState({active: false, restarting: true, width: 0}, () => { this.setState({active: false, restarting: true, top, left, width}, () => {
this.refs.ripple.offsetWidth; //eslint-disable-line no-unused-expressions this.refs.ripple.offsetWidth; //eslint-disable-line no-unused-expressions
this.setState({active: true, restarting: false, top, left, width}); this.setState({active: true, restarting: false});
}); });
}; };
_getDescriptor (pageX, pageY) { _getDescriptor (pageX, pageY) {
const { left, top, height, width } = ReactDOM.findDOMNode(this).getBoundingClientRect(); const {left, top, height, width} = ReactDOM.findDOMNode(this).getBoundingClientRect();
return { return {
left: this.props.centered ? width / 2 : pageX - left + window.scrollX, left: this.props.centered ? 0 : pageX - left - width / 2 + window.scrollX,
top: this.props.centered ? height / 2 : pageY - top + window.scrollY, top: this.props.centered ? 0 : pageY - top - height / 2 + window.scrollY,
width: width * this.props.spread width: width * this.props.spread
}; };
} }
render () { render () {
const { left, top, width } = this.state; const { left, top, width } = this.state;
const rippleStyle = {left, top, width, height: width}; const scale = this.state.restarting ? 0 : 1;
let rippleStyle = {width, height: width};
if (!this.props.loading) {
rippleStyle = prefixer({
transform: `translate3d(${-width / 2 + left}px, ${-width / 2 + top}px, 0) scale(${scale})`
}, rippleStyle);
}
let className = style[this.props.loading ? 'loading' : 'normal']; let className = style[this.props.loading ? 'loading' : 'normal'];
if (this.state.active) className += ` ${style.active}`; if (this.state.active) className += ` ${style.active}`;
if (this.state.restarting) className += ` ${style.restarting}`; if (this.state.restarting) className += ` ${style.restarting}`;

View File

@ -3,14 +3,13 @@
%ripple { %ripple {
position: absolute; position: absolute;
top: 50%;
left: 50%;
z-index: $z-index-high; z-index: $z-index-high;
pointer-events: none; pointer-events: none;
background-color: currentColor; background-color: currentColor;
border-radius: 50%; border-radius: 50%;
transform-style: preserve-3d; transform-origin: 50% 50%;
transform: translate3d(-50%, -50%, 0);
backface-visibility: hidden;
will-change: width, height, opacity;
} }
.wrapper { .wrapper {
@ -25,26 +24,24 @@
.normal { .normal {
@extend %ripple; @extend %ripple;
width: 0;
height: 0;
opacity: $ripple-final-opacity;
transition-timing-function: $animation-curve-linear-out-slow-in;
transition-duration: $ripple-duration; transition-duration: $ripple-duration;
transition-property: height, width; &.restarting {
&:not(.active) { opacity: $ripple-final-opacity;
transition-property: none;
}
&.active {
opacity: $ripple-final-opacity;
transition-property: transform;
}
&:not(.active):not(.restarting) {
opacity: 0; opacity: 0;
transition-property: height, width, opacity; transition-property: opacity, transform;
&.restarting {
transition-property: none;
}
} }
} }
.loading { .loading {
@extend %ripple; @extend %ripple;
@include ripple-loading(ripple, $ripple-size, $ripple-size); @include ripple-loading(ripple, $ripple-size, $ripple-size);
top: 50%;
left: 50%;
width: $ripple-size; width: $ripple-size;
height: $ripple-size; height: $ripple-size;
opacity: $ripple-final-opacity; opacity: $ripple-final-opacity;

View File

@ -103,7 +103,7 @@ describe('Slider', function () {
describe('#events', function () { describe('#events', function () {
beforeEach(function () { beforeEach(function () {
onChange = sinon.spy(); onChange = sinon.spy();
props = { min: -500, max: 500, onChange: onChange }; props = { min: -500, max: 500, onChange };
state = { sliderStart: 0, sliderLength: 1000 }; state = { sliderStart: 0, sliderLength: 1000 };
slider = utils.renderComponent(Slider, props, state); slider = utils.renderComponent(Slider, props, state);
slider.handleResize = (event, callback) => { callback(); }; slider.handleResize = (event, callback) => { callback(); };

View File

@ -14,6 +14,7 @@
line-height: $font-size-small; line-height: $font-size-small;
color: $tooltip-color; color: $tooltip-color;
text-align: center; text-align: center;
text-transform: none;
background: $tooltip-background; background: $tooltip-background;
border-radius: $tooltip-border-radius; border-radius: $tooltip-border-radius;
transform: scale(0); transform: scale(0);

View File

@ -25,8 +25,8 @@ function addPrefixesTo (style, property, value) {
return style; return style;
} }
function prefixer (style) { function prefixer (style, defaultValue = {}) {
const _style = {}; const _style = defaultValue;
for (const property in style) { for (const property in style) {
_style[property] = style[property]; _style[property] = style[property];
if (properties[property]) { if (properties[property]) {

View File

@ -39,7 +39,7 @@ const Preview = React.createClass({
setTimeout () { setTimeout () {
clearTimeout(this.timeoutID); clearTimeout(this.timeoutID);
this.timeoutID = setTimeout.apply(null, arguments); this.timeoutID = setTimeout(...arguments);
}, },
compileCode () { compileCode () {
@ -72,7 +72,7 @@ const Preview = React.createClass({
const compiledCode = this.compileCode(); const compiledCode = this.compileCode();
/*eslint-disable no-eval*/ /*eslint-disable no-eval*/
const Component = eval(compiledCode).apply(null, scope); const Component = eval(compiledCode)(...scope);
ReactDOM.render(Component, mountNode); ReactDOM.render(Component, mountNode);
if (this.state.error) { if (this.state.error) {
this.setState({error: null}); this.setState({error: null});

View File

@ -1,7 +1,7 @@
{ {
"name": "react-toolbox", "name": "react-toolbox",
"version": "0.12.12", "version": "0.12.12",
"homepage": "www.react-toolbox.com", "homepage": "http://www.react-toolbox.com",
"description": "A set of React components implementing Google's Material Design specification with the power of CSS Modules.", "description": "A set of React components implementing Google's Material Design specification with the power of CSS Modules.",
"author": "React Toolbox Team (http://github.com/react-toolbox)", "author": "React Toolbox Team (http://github.com/react-toolbox)",
"contributors": [ "contributors": [
@ -53,15 +53,16 @@
"autoprefixer": "^6.0.3", "autoprefixer": "^6.0.3",
"babel": "^5.8.23", "babel": "^5.8.23",
"babel-core": "^5.8.23", "babel-core": "^5.8.23",
"babel-eslint": "^4.1.3", "babel-eslint": "^4.1.5",
"babel-loader": "^5.3.2", "babel-loader": "^5.3.2",
"babel-plugin-react-transform": "^1.1.1", "babel-plugin-react-transform": "^1.1.1",
"core-js": "^1.2.6", "core-js": "^1.2.6",
"cpx": "^1.2.1", "cpx": "^1.2.1",
"cross-env": "^1.0.4", "cross-env": "^1.0.4",
"css-loader": "^0.21.0", "css-loader": "^0.21.0",
"eslint": "^1.7.3", "eslint": "^1.10.1",
"eslint-plugin-react": "^3.3.1", "eslint-plugin-babel": "^2.1.1",
"eslint-plugin-react": "^3.10.0",
"expect": "^1.8.0", "expect": "^1.8.0",
"express": "^4.13.3", "express": "^4.13.3",
"extract-text-webpack-plugin": "^0.8.2", "extract-text-webpack-plugin": "^0.8.2",