Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AppBar] waterfall behaviour #1321

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/src/app/components/AppBar/ExampleIcon.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const AppBarExampleIcon = React.createClass({
return (
<AppBar
title="Title"
position="static"
iconClassNameRight="muidocs-icon-navigation-expand-more"
/>
);
Expand Down
1 change: 1 addition & 0 deletions docs/src/app/components/AppBar/ExampleIconButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const AppBarExampleIconButton = React.createClass({
return (
<AppBar
title={<span style={styles.title} onTouchTap={handleTouchTap}>Title</span>}
position="static"
iconElementLeft={<IconButton><NavigationClose /></IconButton>}
iconElementRight={<FlatButton label="Save" />}
/>
Expand Down
1 change: 1 addition & 0 deletions docs/src/app/components/AppBar/ExampleIconMenu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const AppBarExampleIconMenu = React.createClass({
return (
<AppBar
title="Title"
position="static"
iconElementLeft={<IconButton><NavigationClose /></IconButton>}
iconElementRight={
<IconMenu
Expand Down
94 changes: 94 additions & 0 deletions docs/src/app/components/AppBar/ExampleWaterfall.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import {AppBar} from 'material-ui';

import IconButton from 'icon-button';
import MoreVertIcon from 'svg-icons/navigation/more-vert';
import ArrowBack from 'svg-icons/navigation/arrow-back';

const MIN_HEIGHT = 64;
const MAX_HEIGHT = 210;

const AppBarWaterfallExample = React.createClass({

propTypes: {
onBack: React.PropTypes.func,
},

getInitialState() {
return {
height: MAX_HEIGHT,
};
},

render() {
const styles = this.getStyles();
return (
<AppBar
position="waterfall"
waterfall={{
minHeight: MIN_HEIGHT,
maxHeight: MAX_HEIGHT,
onHeightChange: this.onHeightChange,
children: (<div
style={styles.logoWrap}>
<img
ref={el => { this.logoEl = el; }}
style={styles.logo}
src="images/material-ui-logo.svg"/>
</div>),
}}
title={
<div
style={styles.title}
ref={el => { this.titleEl = el; }}>
Waterfall AppBar
</div>
}
iconElementLeft={
<IconButton onClick={this.onBackClick}>
<ArrowBack />
</IconButton>
}
iconElementRight={
<IconButton>
<MoreVertIcon/>
</IconButton>
}
/>
);
},

onHeightChange({height}) {
this.setState({height});
},

onBackClick() {
this.props.onBack();
},

getInterpolation(height) {
return (height - MIN_HEIGHT) / (MAX_HEIGHT - MIN_HEIGHT);
},

getStyles() {
const interpolation = this.getInterpolation(this.state.height);
return {
logoWrap: {
overflow: 'hidden',
},
logo: {
height: 120,
margin: '0 auto',
display: 'block',
transformOrigin: '25% 100% 0',
transform: `translate3d(80px,0,0) scale3d(${interpolation}, ${interpolation}, 1)`,
opacity: interpolation,
},
title: {
opacity: 1 - interpolation,
},
};
},
});

export default AppBarWaterfallExample;
93 changes: 93 additions & 0 deletions docs/src/app/components/AppBar/ExampleWaterfallOptimized.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import {AppBar} from 'material-ui';

import IconButton from 'icon-button';
import MoreVertIcon from 'svg-icons/navigation/more-vert';
import ArrowBack from 'svg-icons/navigation/arrow-back';

const MIN_HEIGHT = 64;
const MAX_HEIGHT = 210;

const AppBarWaterfallExample = React.createClass({

propTypes: {
onBack: React.PropTypes.func,
},

render() {
const styles = this.getStyles();
return (
<AppBar
position="waterfall"
waterfall={{
minHeight: MIN_HEIGHT,
maxHeight: MAX_HEIGHT,
onHeightChange: this.onHeightChange,
children: (<div
style={styles.logoWrap}>
<img
ref={el => { this.logoEl = el; }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having to rely on a ref to optimize the component seems weird.
Can't we change the API. I think that it would be better to have a children property
as a function that take the height as an input and return a component.
This way, we would only need one example and not two: a simple and optimized one.
We could probably get ride of the onHeightChange property.

The pattern I'm proposing is directly inspired by react-motion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a function with height as parameter seems a good idea at first. I started to implement this feature exactly this way, but it didn't help the performance too much. I even tried react-motion, but still not better. My use-case was to use it for a web app and I really wanted a smooth > 30fps transition on a mid-range phone. The only way to do this is by directly modifying the styles on DOM elements, unfortunately. Probably this is why LeftNav have a similar approach. I think one serious bottleneck with a seState -> render solution is the current inline styling with aggressive and mostly unnecessary prefixing. Maybe some immutable data structures or memoization could help, but that's a long shot.
Anyway onHeightChange is still necessary for transitions on elements that are not part of the waterfall children, like AppBar title, or, in my app I had a FAB that really needed this callback.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for investigating this option.

I even tried react-motion, but still not better

I'm curious, I'm using react-motion here http://oliviertassinari.github.io/react-swipeable-views/.
Would you say it's smooth?

Anyway onHeightChange is still necessary

Sure, I agree.

style={styles.logo}
src="images/material-ui-logo.svg"/>
</div>),
}}
title={
<div
style={styles.title}
ref={el => { this.titleEl = el; }}>
Waterfall AppBar
</div>
}
iconElementLeft={
<IconButton onClick={this.onBackClick}>
<ArrowBack />
</IconButton>
}
iconElementRight={
<IconButton>
<MoreVertIcon/>
</IconButton>
}
/>
);
},

onHeightChange({height}) {
let interpolation = this.getInterpolation(height);

// For best performance, we will directly modify style on DOM elements
this.logoEl.style.transform = `translate3d(80px,0,0) scale3d(${interpolation}, ${interpolation}, 1)`;

this.logoEl.style.opacity = interpolation;
this.titleEl.style.opacity = 1 - interpolation;
},

onBackClick() {
this.props.onBack();
},

getInterpolation(height) {
return (height - MIN_HEIGHT) / (MAX_HEIGHT - MIN_HEIGHT);
},

getStyles() {
return {
logoWrap: {
overflow: 'hidden',
},
logo: {
height: 120,
margin: '0 auto',
display: 'block',
transformOrigin: '25% 100% 0',
transform: `translate3d(80px,0,0) scale3d(1, 1, 1)`,
opacity: 1,
},
title: {
opacity: 0,
},
};
},
});

export default AppBarWaterfallExample;
Loading