From fd45282e43e95bdef69340e2abf0e873c40bf167 Mon Sep 17 00:00:00 2001 From: "Poulter, Roland" Date: Thu, 2 Jul 2015 12:17:32 -0700 Subject: [PATCH] Improves hideable menu transition quality --- src/drop-down-menu.jsx | 1 + src/menu/menu.jsx | 150 +++++++++++++++++++++++++---------------- 2 files changed, 94 insertions(+), 57 deletions(-) diff --git a/src/drop-down-menu.jsx b/src/drop-down-menu.jsx index 05fc160c9e11e6..5290eb5f2ff8dd 100644 --- a/src/drop-down-menu.jsx +++ b/src/drop-down-menu.jsx @@ -60,6 +60,7 @@ let DropDownMenu = React.createClass({ }, componentWillReceiveProps(nextProps) { + if (this.props.autoWidth) this._setWidth(); if (nextProps.hasOwnProperty('value') || nextProps.hasOwnProperty('valueLink')) { return; } else if (nextProps.hasOwnProperty('selectedIndex')) { diff --git a/src/menu/menu.jsx b/src/menu/menu.jsx index 8dc118857e46f8..06d7965f27f53a 100644 --- a/src/menu/menu.jsx +++ b/src/menu/menu.jsx @@ -280,7 +280,6 @@ var Menu = React.createClass({ paddingRight: this.context.muiTheme.component.menuSubheader.padding }, hideable: { - opacity: (this.props.visible) ? 1 : 0, overflow: 'hidden', position: 'absolute', top: 0, @@ -427,71 +426,108 @@ var Menu = React.createClass({ }, _setKeyWidth(el) { - el.style.width = 'auto'; + //Update the menu width + let menuWidth = '100%'; - let menuWidth = this.props.autoWidth ? - KeyLine.getIncrementalDim(el.offsetWidth) + 'px' : - '100%'; + if (this.props.autoWidth) { + el.style.width = 'auto'; + menuWidth = KeyLine.getIncrementalDim(el.offsetWidth) + 'px'; + } - //Update the menu width - Dom.withoutTransition(el, () => { - el.style.width = menuWidth; - }); + el.style.width = menuWidth; }, _renderVisibility() { - let el; - if (this.props.hideable) { - el = React.findDOMNode(this); - let container = React.findDOMNode(this.refs.paperContainer); - - if (this.props.visible) { - //Hide the element and allow the browser to automatically resize it. - el.style.transition = ''; - el.style.visibility = 'hidden'; - el.style.height = 'auto'; - - //Determine the height of the menu. - let padding = this.getSpacing().desktopGutterMini; - let height = el.offsetHeight + - //Add padding to the offset height, because it is not yet set in the style. - (padding * 2); - - //Unhide the menu with the height set back to zero. - el.style.height = '0px'; - el.style.visibility = 'visible'; - - //Add transition if it is not already defined. - el.style.transition = Transitions.easeOut(); - - //Open the menu - setTimeout(() => { - // Yeild to the DOM, then apply height and padding. This makes the transition smoother. - el.style.paddingTop = padding + 'px'; - el.style.paddingBottom = padding + 'px'; - el.style.height = height + 'px'; - - //Set the overflow to visible after the animation is done so - //that other nested menus can be shown - CssEvent.onTransitionEnd(el, () => { - //Make sure the menu is open before setting the overflow. - //This is to accout for fast clicks - if (this.props.visible) container.style.overflow = 'visible'; - el.focus(); - }); - }, 0); - } - else { - //Close the menu - el.style.height = '0px'; - el.style.paddingTop = '0px'; - el.style.paddingBottom = '0px'; + if (this.props.visible) this._expandHideableMenu(); + else this._collapseHideableMenu(); + } + }, + + _expandHideableMenu() { + let el = React.findDOMNode(this); + let container = React.findDOMNode(this.refs.paperContainer); + let padding = this.getSpacing().desktopGutterMini; + let height = this._getHiddenMenuHeight(el, padding); + + //Add transition + if (!el.style.transition) { + el.style.transition = Transitions.easeOut(); + } + + this._nextAnimationFrame(() => { + container.style.overflow = 'hidden'; + + // Yeild to the DOM, then apply height and padding. This makes the transition smoother. + el.style.paddingTop = padding + 'px'; + el.style.paddingBottom = padding + 'px'; + el.style.height = height + 'px'; + el.style.opacity = 1; + + //Set the overflow to visible after the animation is done so + //that other nested menus can be shown + CssEvent.onTransitionEnd(el, () => { + //Make sure the menu is open before setting the overflow. + //This is to accout for fast clicks + if (this.props.visible) container.style.overflow = 'visible'; + el.style.transition = null; + el.focus(); + }); + }); + }, + + _getHiddenMenuHeight(el, padding) { + //Add padding to the offset height, because it is not yet set in the style. + let height = padding * 2; + //Hide the element and allow the browser to automatically resize it. + el.style.visibility = 'hidden'; + el.style.height = 'auto'; + + //Determine the height of the menu. + height += el.offsetHeight; + + //Unhide the menu with the height set back to zero. + el.style.height = '0px'; + el.style.visibility = 'visible'; + + return height; + }, + + _collapseHideableMenu() { + let el = React.findDOMNode(this); + let container = React.findDOMNode(this.refs.paperContainer); + let originalOpacity = el.style.opacity; + + //Add transition + if (!el.style.transition && originalOpacity !== '') { + el.style.transition = Transitions.easeOut(); + } + + this._nextAnimationFrame(function () { + container.style.overflow = 'hidden'; + + //Close the menu + el.style.opacity = 0; + el.style.height = '0px'; + el.style.paddingTop = '0px'; + el.style.paddingBottom = '0px'; + + let end = () => { //Set the overflow to hidden so that animation works properly - container.style.overflow = 'hidden'; - } + el.style.transition = null; + }; + + if (originalOpacity === '') end(); + else CssEvent.onTransitionEnd(el, end); + }); + }, + + _nextAnimationFrame(func) { + if (window.requestAnimationFrame) { + return window.requestAnimationFrame(func); } + return setTimeout(func, 16); }, _onNestedItemTap(e, index, menuItem) {