Skip to content

Commit

Permalink
fix(panel): allow transform to be animated on an offset panel
Browse files Browse the repository at this point in the history
Previously, if a panel had a position with an offset (e.g. `$mdPanel.newPanelPosition().center()`), the positioning would break any `transform` animations on the panel. This was due to the fact that `mdPanel` uses inline `transform` styles to do the offsetting. These changes introduce a wrapper around the panel (`.md-panel-inner-wrapper`), which will handle all of the positioning, allowing for any animations to be applied to the `.md-panel` itself.

Relates to angular#9641.
Fixes angular#9905.
  • Loading branch information
crisbeto committed Jan 1, 2017
1 parent 8801ef8 commit ae4bdca
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 108 deletions.
110 changes: 62 additions & 48 deletions src/components/panel/panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1044,10 +1044,10 @@ MdPanelService.prototype._closeFirstOpenedPanel = function(groupName) {


/**
* Wraps the users template in two elements, md-panel-outer-wrapper, which
* covers the entire attachTo element, and md-panel, which contains only the
* template. This allows the panel control over positioning, animations,
* and similar properties.
* Wraps the user's template in three elements:
* - md-panel-outer-wrapper - covers the entire `attachTo` element.
* - md-panel-inner-wrapper - handles the positioning.
* - md-panel - contains the user's content and deals with the animations.
* @param {string} origTemplate The original template.
* @returns {string} The wrapped template.
* @private
Expand All @@ -1059,26 +1059,32 @@ MdPanelService.prototype._wrapTemplate = function(origTemplate) {
// height and width for positioning.
return '' +
'<div class="md-panel-outer-wrapper">' +
' <div class="md-panel" style="left: -9999px;">' + template + '</div>' +
'<div class="md-panel-inner-wrapper" style="left: -9999px;">' +
'<div class="md-panel">' + template + '</div>' +
'</div>' +
'</div>';
};


/**
* Wraps a content element in a md-panel-outer wrapper and
* positions it off-screen. Allows for proper control over positoning
* and animations.
* Wraps a content element in a `md-panel-outer-wrapper`, as well as
* a `md-panel-inner-wrapper`, and positions it off-screen. Allows for
* proper control over positoning and animations.
* @param {!angular.JQLite} contentElement Element to be wrapped.
* @return {!angular.JQLite} Wrapper element.
* @private
*/
MdPanelService.prototype._wrapContentElement = function(contentElement) {
var wrapper = angular.element('<div class="md-panel-outer-wrapper">');
var outerWrapper = angular.element(
'<div class="md-panel-outer-wrapper">' +
'<div class="md-panel-inner-wrapper" style="left: -9999px;"></div>' +
'</div>'
);

contentElement.addClass('md-panel').css('left', '-9999px');
wrapper.append(contentElement);
contentElement.addClass('md-panel');
outerWrapper.children().eq(0).append(contentElement);

return wrapper;
return outerWrapper;
};


Expand Down Expand Up @@ -1145,6 +1151,9 @@ function MdPanelRef(config, $injector) {
/** @type {!angular.JQLite|undefined} */
this.panelEl;

/** @type {!angular.JQLite|undefined} */
this.innerWrapper;

/**
* Whether the panel is attached. This is synchronous. When attach is called,
* isAttached is set to true. When detach is called, isAttached is set to
Expand Down Expand Up @@ -1587,6 +1596,11 @@ MdPanelRef.prototype._compile = function() {
);
}

// Save a reference to the inner wrapper.
self.innerWrapper = angular.element(
self.panelContainer[0].querySelector('.md-panel-inner-wrapper')
);

// Save a reference to the cleanup function from the compiler.
self._compilerCleanup = compileData.cleanup;

Expand Down Expand Up @@ -1662,14 +1676,14 @@ MdPanelRef.prototype._addStyles = function() {
var self = this;
return this._$q(function(resolve) {
self.panelContainer.css('z-index', self.config['zIndex']);
self.panelEl.css('z-index', self.config['zIndex'] + 1);
self.innerWrapper.css('z-index', self.config['zIndex'] + 1);

var hideAndResolve = function() {
// Theme the element and container.
self._setTheming();

// Remove left: -9999px and add hidden class.
self.panelEl.css('left', '');
self.innerWrapper.css('left', '');
self.panelContainer.addClass(MD_PANEL_HIDDEN);

resolve(self);
Expand Down Expand Up @@ -1736,26 +1750,26 @@ MdPanelRef.prototype._updatePosition = function(init) {
var positionConfig = this.config['position'];

if (positionConfig) {
positionConfig._setPanelPosition(this.panelEl);
positionConfig._setPanelPosition(this.innerWrapper);

// Hide the panel now that position is known.
if (init) {
this.panelContainer.addClass(MD_PANEL_HIDDEN);
}

this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.TOP,
positionConfig.getTop()
);
this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.BOTTOM,
positionConfig.getBottom()
);
this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.LEFT,
positionConfig.getLeft()
);
this.panelEl.css(
this.innerWrapper.css(
MdPanelPosition.absPosition.RIGHT,
positionConfig.getRight()
);
Expand Down Expand Up @@ -2662,38 +2676,38 @@ MdPanelPosition.prototype.getTransform = function() {


/**
* Sets the `transform` value for a panel element.
* @param {!angular.JQLite} panelEl
* Sets the `transform` value for an element.
* @param {!angular.JQLite} el
* @returns {!angular.JQLite}
* @private
*/
MdPanelPosition.prototype._setTransform = function(panelEl) {
return panelEl.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
MdPanelPosition.prototype._setTransform = function(el) {
return el.css(this._$mdConstant.CSS.TRANSFORM, this.getTransform());
};


/**
* True if the panel is completely on-screen with this positioning; false
* otherwise.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @return {boolean}
* @private
*/
MdPanelPosition.prototype._isOnscreen = function(panelEl) {
MdPanelPosition.prototype._isOnscreen = function(el) {
// this works because we always use fixed positioning for the panel,
// which is relative to the viewport.
var left = parseInt(this.getLeft());
var top = parseInt(this.getTop());

if (this._translateX.length || this._translateY.length) {
var prefixedTransform = this._$mdConstant.CSS.TRANSFORM;
var offsets = getComputedTranslations(panelEl, prefixedTransform);
var offsets = getComputedTranslations(el, prefixedTransform);
left += offsets.x;
top += offsets.y;
}

var right = left + panelEl[0].offsetWidth;
var bottom = top + panelEl[0].offsetHeight;
var right = left + el[0].offsetWidth;
var bottom = top + el[0].offsetHeight;

return (left >= 0) &&
(top >= 0) &&
Expand Down Expand Up @@ -2733,53 +2747,53 @@ MdPanelPosition.prototype._reduceTranslateValues =
/**
* Sets the panel position based on the created panel element and best x/y
* positioning.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @private
*/
MdPanelPosition.prototype._setPanelPosition = function(panelEl) {
// Remove the "position adjusted" class in case it has been added before.
panelEl.removeClass('_md-panel-position-adjusted');
MdPanelPosition.prototype._setPanelPosition = function(el) {
// Remove the class in case it has been added before.
el.removeClass('_md-panel-position-adjusted');

// Only calculate the position if necessary.
if (this._absolute) {
this._setTransform(panelEl);
this._setTransform(el);
return;
}

if (this._actualPosition) {
this._calculatePanelPosition(panelEl, this._actualPosition);
this._setTransform(panelEl);
this._constrainToViewport(panelEl);
this._calculatePanelPosition(el, this._actualPosition);
this._setTransform(el);
this._constrainToViewport(el);
return;
}

for (var i = 0; i < this._positions.length; i++) {
this._actualPosition = this._positions[i];
this._calculatePanelPosition(panelEl, this._actualPosition);
this._setTransform(panelEl);
this._calculatePanelPosition(el, this._actualPosition);
this._setTransform(el);

if (this._isOnscreen(panelEl)) {
if (this._isOnscreen(el)) {
return;
}
}

this._constrainToViewport(panelEl);
this._constrainToViewport(el);
};


/**
* Constrains a panel's position to the viewport.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @private
*/
MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
MdPanelPosition.prototype._constrainToViewport = function(el) {
var margin = MdPanelPosition.viewportMargin;
var initialTop = this._top;
var initialLeft = this._left;

if (this.getTop()) {
var top = parseInt(this.getTop());
var bottom = panelEl[0].offsetHeight + top;
var bottom = el[0].offsetHeight + top;
var viewportHeight = this._$window.innerHeight;

if (top < margin) {
Expand All @@ -2791,7 +2805,7 @@ MdPanelPosition.prototype._constrainToViewport = function(panelEl) {

if (this.getLeft()) {
var left = parseInt(this.getLeft());
var right = panelEl[0].offsetWidth + left;
var right = el[0].offsetWidth + left;
var viewportWidth = this._$window.innerWidth;

if (left < margin) {
Expand All @@ -2802,7 +2816,7 @@ MdPanelPosition.prototype._constrainToViewport = function(panelEl) {
}

// Class that can be used to re-style the panel if it was repositioned.
panelEl.toggleClass(
el.toggleClass(
'_md-panel-position-adjusted',
this._top !== initialTop || this._left !== initialLeft
);
Expand Down Expand Up @@ -2841,13 +2855,13 @@ MdPanelPosition.prototype._bidi = function(position) {
/**
* Calculates the panel position based on the created panel element and the
* provided positioning.
* @param {!angular.JQLite} panelEl
* @param {!angular.JQLite} el
* @param {!{x:string, y:string}} position
* @private
*/
MdPanelPosition.prototype._calculatePanelPosition = function(panelEl, position) {
MdPanelPosition.prototype._calculatePanelPosition = function(el, position) {

var panelBounds = panelEl[0].getBoundingClientRect();
var panelBounds = el[0].getBoundingClientRect();
var panelWidth = panelBounds.width;
var panelHeight = panelBounds.height;

Expand Down
26 changes: 15 additions & 11 deletions src/components/panel/panel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@
width: 100%;
}

._md-panel-hidden {
display: none;
.md-panel-inner-wrapper {
position: fixed;
}

._md-panel-fullscreen {
border-radius: 0;
left: 0;
min-height: 100%;
min-width: 100%;
position: fixed;
top: 0;
._md-panel-hidden {
display: none;
}

// Only used when no animations are present.
Expand All @@ -27,7 +22,7 @@

.md-panel {
opacity: 0;
position: fixed;
position: relative;

&._md-panel-shown {
// Only used when custom animations are present.
Expand All @@ -53,7 +48,7 @@

&._md-panel-backdrop {
height: 100%;
position: absolute;
position: fixed;
width: 100%;
}

Expand All @@ -66,3 +61,12 @@
transition: opacity $material-leave-duration $material-leave-timing-function;
}
}

._md-panel-fullscreen {
border-radius: 0;
left: 0;
min-height: 100%;
min-width: 100%;
position: fixed;
top: 0;
}
Loading

0 comments on commit ae4bdca

Please sign in to comment.