From 79915995bd35c80fabc657d40a2b51de6123e547 Mon Sep 17 00:00:00 2001 From: Thomas Burleson Date: Thu, 3 Sep 2015 10:07:47 -0500 Subject: [PATCH] fix(progressLinear, progressCircular): sync logic, fix linear animations, perf upgrades synchronize progressLinear with similar logic used in progressCircular. * improve animation performances * watch md-mode for changes * refactor animation SCSS * enable hiding and no-animations with undefined/empty md-mode attributes * for both indicators, use `display:inline-block;` * update demos with enable switch * fix query mode * update Select to use enhanced progressCircular component * fix autocomplete styling of progress-linear.md-mode-indeterminate BREAKING-CHANGES Before: ```css md-progress-linear { display: block; } md-progress-circular { // display not set // position not set } ``` ```css md-progress-linear { display: inline-block; position: relative; } md-progress-circular { display: inline-block; position: relative; } ``` Fixes #4421. Fixes #4409. Fixes #2540. Fixes #2364. Fixes #1926. Fixes #3802. --- src/components/autocomplete/autocomplete.scss | 5 +- .../demoBasicUsage/index.html | 2 +- .../progressCircular/demoBasicUsage/style.css | 4 + .../progressCircular/progress-circular.js | 28 +++- .../progressCircular/progress-circular.scss | 107 +++++++------ .../progressLinear/demoBasicUsage/index.html | 54 ++++++- .../progressLinear/demoBasicUsage/script.js | 41 +++-- .../progressLinear/demoBasicUsage/style.css | 30 +++- .../progressLinear/progress-linear.js | 128 +++++++++++----- .../progressLinear/progress-linear.scss | 145 ++++++++++-------- src/components/select/select.js | 6 +- 11 files changed, 366 insertions(+), 184 deletions(-) diff --git a/src/components/autocomplete/autocomplete.scss b/src/components/autocomplete/autocomplete.scss index 4d43d76a280..1d62e0212b0 100644 --- a/src/components/autocomplete/autocomplete.scss +++ b/src/components/autocomplete/autocomplete.scss @@ -71,15 +71,14 @@ md-autocomplete { &.md-menu-showing { z-index: $z-index-backdrop + 1; } - md-progress-linear[md-mode=indeterminate] { + md-progress-linear .md-mode-indeterminate { position: absolute; - bottom: 0; left: 0; width: 100%; + top: 20px; left: 0; width: 100%; height: 3px; transition: none; .md-container { transition: none; - top: auto; height: 3px; } &.ng-enter { diff --git a/src/components/progressCircular/demoBasicUsage/index.html b/src/components/progressCircular/demoBasicUsage/index.html index f8e03ea45a3..e82429359d3 100644 --- a/src/components/progressCircular/demoBasicUsage/index.html +++ b/src/components/progressCircular/demoBasicUsage/index.html @@ -28,7 +28,7 @@

Theming

-

Show Progress Circular Indicators:

+

Show Progress Circular Indicators:

Off
h5 { padding-top: 8px; } + +#loaders > p { + margin-right: 20px; +} diff --git a/src/components/progressCircular/progress-circular.js b/src/components/progressCircular/progress-circular.js index 34db74074cd..9cdf82b84ad 100644 --- a/src/components/progressCircular/progress-circular.js +++ b/src/components/progressCircular/progress-circular.js @@ -101,7 +101,7 @@ function MdProgressCircularDirective($mdConstant, $mdTheming, $mdUtil) { var percentValue = clamp(value); element.attr('aria-valuenow', percentValue); - if (attr.mdMode == "determinate") { + if (mode() == MODE_DETERMINATE) { animateIndicator(percentValue); } }); @@ -122,6 +122,11 @@ function MdProgressCircularDirective($mdConstant, $mdTheming, $mdUtil) { break; default: spinnerWrapper.addClass('ng-hide'); + if ( lastMode ) { + spinnerWrapper.removeClass( lastMode ); + lastMode = undefined; + } + break; } }); @@ -136,6 +141,8 @@ function MdProgressCircularDirective($mdConstant, $mdTheming, $mdUtil) { * - use attribute selectors which had poor performances in IE */ function animateIndicator(value) { + if ( !mode() ) return; + leftC = leftC || angular.element(element[0].querySelector('.md-left > .md-half-circle')); rightC = rightC || angular.element(element[0].querySelector('.md-right > .md-half-circle')); gap = gap || angular.element(element[0].querySelector('.md-gap')); @@ -174,6 +181,25 @@ function MdProgressCircularDirective($mdConstant, $mdTheming, $mdUtil) { // should return ratio; DEFAULT_PROGRESS_SIZE === 100px is default size return (value > 1) ? value / DEFAULT_PROGRESS_SIZE : value; } + + /** + * Is the md-mode a valid option? + */ + function mode() { + var value = attr.mdMode; + if ( value ) { + switch(value) { + case MODE_DETERMINATE : + case MODE_INDETERMINATE : + break; + default: + value = undefined; + break; + } + } + return value; + } + } /** diff --git a/src/components/progressCircular/progress-circular.scss b/src/components/progressCircular/progress-circular.scss index b99d119a911..337e7de45d8 100644 --- a/src/components/progressCircular/progress-circular.scss +++ b/src/components/progressCircular/progress-circular.scss @@ -7,63 +7,72 @@ $progress-border-width : 10px; $progress-circular-size : 10 * $progress-border-width !default; md-progress-circular { + display: inline-block; + position: relative; + width: $progress-circular-size; height: $progress-circular-size; - display: block; - position: relative; + padding-top: 0 !important; margin-bottom: 0 !important; - overflow: hidden; + transform: scale(0.5); - .md-inner { - width: $progress-circular-size; - height: $progress-circular-size; + .md-spinner-wrapper { position: relative; - .md-gap { - position: absolute; - left: $progress-circular-size * 0.5 - 1; - right: $progress-circular-size * 0.5 - 1; - top: 0; - bottom: 0; - border-top-width: $progress-border-width; - border-top-style: solid; - box-sizing: border-box; - } - .md-left, .md-right { - position: absolute; - top: 0; - height: $progress-circular-size; - width: $progress-circular-size * 0.50; - overflow: hidden; - .md-half-circle { - position: absolute; - top: 0; - width: $progress-circular-size; - height: $progress-circular-size; - box-sizing: border-box; - border-width: $progress-border-width; - border-style: solid; - border-bottom-color: transparent; - border-radius: 50%; - } - } - .md-left { - left: 0; - .md-half-circle { - left: 0; - border-right-color: transparent; - } - } - .md-right { - right: 0; - .md-half-circle { - right: 0; - border-left-color: transparent; - } - } + overflow: hidden; + display:block; + + .md-inner { + width: $progress-circular-size; + height: $progress-circular-size; + position: relative; + .md-gap { + position: absolute; + left: $progress-circular-size * 0.5 - 1; + right: $progress-circular-size * 0.5 - 1; + top: 0; + bottom: 0; + border-top-width: $progress-border-width; + border-top-style: solid; + box-sizing: border-box; + } + .md-left, .md-right { + position: absolute; + top: 0; + height: $progress-circular-size; + width: $progress-circular-size * 0.50; + overflow: hidden; + .md-half-circle { + position: absolute; + top: 0; + width: $progress-circular-size; + height: $progress-circular-size; + box-sizing: border-box; + border-width: $progress-border-width; + border-style: solid; + border-bottom-color: transparent; + border-radius: 50%; + } + } + .md-left { + left: 0; + .md-half-circle { + left: 0; + border-right-color: transparent; + } + } + .md-right { + right: 0; + .md-half-circle { + right: 0; + border-left-color: transparent; + } + } + } } + .md-spinner-wrapper.md-mode-indeterminate { animation: outer-rotate $progress-circular-outer-duration linear infinite; .md-inner { @@ -88,7 +97,7 @@ md-progress-circular { } } - .ng-hide md-progress-circular, md-progress-circular.ng-hide { + md-progress-circular.ng-hide { .md-spinner-wrapper { animation: none; .md-inner { diff --git a/src/components/progressLinear/demoBasicUsage/index.html b/src/components/progressLinear/demoBasicUsage/index.html index de37045b1c4..7281a56b375 100644 --- a/src/components/progressLinear/demoBasicUsage/index.html +++ b/src/components/progressLinear/demoBasicUsage/index.html @@ -1,15 +1,53 @@ -
+

Determinate

-

For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take.

- -

Buffer indicates loading from the server

- + +

+ For operations where the percentage of the operation completed can be determined, use a determinate indicator. + They give users a quick sense of how long an operation will take. +

+

Indeterminate

-

For operations where the user is asked to wait a moment while something finishes up, and it's not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator.

+

+ For operations where the user is asked to wait a moment while something finishes up, and it's not + necessary to expose what's happening behind the scenes and how long it will take, use an + indeterminate indicator: +

-

Query indicates pre-loading situation until the loading can actually be made

- + +

Buffer

+

+ For operations where the user wants to indicate some activity or loading from the server, use the buffer indicator: +

+ + +

Query

+

+ For situations where the user wants to indicate pre-loading (until the loading can actually be made), use the query indicator: +

+ + + +
+ +
+ +

'Query' & 'Buffer' linear progress indicators:

+ +
Off
+ +
On
+
+
+ +

+ Note: the above switch simply sets the md-mode = "" to easily + disable the animations and hide the Query and Buffer progress indicators.

+
diff --git a/src/components/progressLinear/demoBasicUsage/script.js b/src/components/progressLinear/demoBasicUsage/script.js index a2b6c4e29b6..1635eba2258 100644 --- a/src/components/progressLinear/demoBasicUsage/script.js +++ b/src/components/progressLinear/demoBasicUsage/script.js @@ -2,20 +2,41 @@ angular.module('progressLinearDemo1', ['ngMaterial']) .config(function($mdThemingProvider) { }) .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) { - $scope.mode = 'query'; - $scope.determinateValue = 30; - $scope.determinateValue2 = 30; + var self = this, j= 0, counter = 0; + + self.mode = 'query'; + self.activated = true; + self.determinateValue = 30; + self.determinateValue2 = 30; + + self.modes = [ ]; + + /** + * Turn off or on the 5 themed loaders + */ + self.toggleActivation = function() { + if ( !self.activated ) self.modes = [ ]; + if ( self.activated ) j = counter = 0; + }; $interval(function() { - $scope.determinateValue += 1; - $scope.determinateValue2 += 1.5; - if ($scope.determinateValue > 100) { - $scope.determinateValue = 30; - $scope.determinateValue2 = 30; - } + self.determinateValue += 1; + self.determinateValue2 += 1.5; + + if (self.determinateValue > 100) self.determinateValue = 30; + if (self.determinateValue2 > 100) self.determinateValue2 = 30; + + // Incrementally start animation the five (5) Indeterminate, + // themed progress circular bars + + if ( (j < 2) && !self.modes[j] && self.activated ) { + self.modes[j] = (j==0) ? 'buffer' : 'query'; + } + if ( counter++ % 4 == 0 ) j++; + }, 100, 0, true); $interval(function() { - $scope.mode = ($scope.mode == 'query' ? 'determinate' : 'query'); + self.mode = (self.mode == 'query' ? 'determinate' : 'query'); }, 7200, 0, true); }]); diff --git a/src/components/progressLinear/demoBasicUsage/style.css b/src/components/progressLinear/demoBasicUsage/style.css index 31123f43e29..eef6bb0c09e 100644 --- a/src/components/progressLinear/demoBasicUsage/style.css +++ b/src/components/progressLinear/demoBasicUsage/style.css @@ -7,6 +7,32 @@ h4 { } md-progress-linear { - padding-top:10px; - margin-bottom:20px; + padding-top: 10px; + margin-bottom: 20px; } + +#loaders > md-switch { + margin: 0; + margin-left: 10px; + margin-top: -10px; +} + +#loaders > h5 { + padding-top: 8px; +} + +#loaders > p { + margin-right: 20px; +} + +p.small { + font-size: 0.8em; + margin-top: -18px; +} + +hr { + width: 100%; + margin-top: 20px; + border-color: rgba(221, 221, 177, 0.1); +} + diff --git a/src/components/progressLinear/progress-linear.js b/src/components/progressLinear/progress-linear.js index e21696dfff2..650d64efe7d 100644 --- a/src/components/progressLinear/progress-linear.js +++ b/src/components/progressLinear/progress-linear.js @@ -15,15 +15,27 @@ angular.module('material.components.progressLinear', [ * @restrict E * * @description - * The linear progress directive is used to make loading content in your app as delightful and painless as possible by minimizing the amount of visual change a user sees before they can view and interact with content. Each operation should only be represented by one activity indicator—for example, one refresh operation should not display both a refresh bar and an activity circle. + * The linear progress directive is used to make loading content + * in your app as delightful and painless as possible by minimizing + * the amount of visual change a user sees before they can view + * and interact with content. * - * For operations where the percentage of the operation completed can be determined, use a determinate indicator. They give users a quick sense of how long an operation will take. + * Each operation should only be represented by one activity indicator + * For example: one refresh operation should not display both a + * refresh bar and an activity circle. * - * For operations where the user is asked to wait a moment while something finishes up, and it’s not necessary to expose what's happening behind the scenes and how long it will take, use an indeterminate indicator. + * For operations where the percentage of the operation completed + * can be determined, use a determinate indicator. They give users + * a quick sense of how long an operation will take. + * + * For operations where the user is asked to wait a moment while + * something finishes up, and it’s not necessary to expose what's + * happening behind the scenes and how long it will take, use an + * indeterminate indicator. * * @param {string} md-mode Select from one of four modes: determinate, indeterminate, buffer or query. * @param {number=} value In determinate and buffer modes, this number represents the percentage of the primary progress bar. Default: 0 - * @param {number=} md-buffer-value In the buffer mode, this number represents the precentage of the secondary progress bar. Default: 0 + * @param {number=} md-buffer-value In the buffer mode, this number represents the percentage of the secondary progress bar. Default: 0 * * @usage * @@ -38,7 +50,11 @@ angular.module('material.components.progressLinear', [ * * */ -function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming) { +function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming, $mdUtil) { + var MODE_DETERMINATE = "determinate", + MODE_INDETERMINATE = "indeterminate", + MODE_BUFFER = "buffer", + MODE_QUERY = "query"; return { restrict: 'E', @@ -59,57 +75,85 @@ function MdProgressLinearDirective($$rAF, $mdConstant, $mdTheming) { } function postLink(scope, element, attr) { $mdTheming(element); - var bar1Style = element[0].querySelector('.md-bar1').style, - bar2Style = element[0].querySelector('.md-bar2').style, - container = angular.element(element[0].querySelector('.md-container')); + var lastMode, toVendorCSS = $mdUtil.dom.animator.toCss; + var bar1 = angular.element(element[0].querySelector('.md-bar1')), + bar2 = angular.element(element[0].querySelector('.md-bar2')), + container = angular.element(element[0].querySelector('.md-container')); attr.$observe('value', function(value) { - if (attr.mdMode == 'query') { - return; - } + var percentValue = clamp(value); + element.attr('aria-valuenow', percentValue); - var clamped = clamp(value); - element.attr('aria-valuenow', clamped); - bar2Style[$mdConstant.CSS.TRANSFORM] = transforms[clamped]; + animateIndicator(bar2, percentValue); }); attr.$observe('mdBufferValue', function(value) { - bar1Style[$mdConstant.CSS.TRANSFORM] = transforms[clamp(value)]; + animateIndicator(bar1, clamp(value)); }); - $$rAF(function() { - container.addClass('md-ready'); + attr.$observe('mdMode',function(mode){ + switch( mode ) { + case MODE_QUERY: + case MODE_BUFFER: + case MODE_DETERMINATE: + case MODE_INDETERMINATE: + container.removeClass('ng-hide'); + + // Inject class selector instead of attribute selector + // (@see layout.js changes for IE performance issues) + + if ( lastMode ) container.removeClass( lastMode ); + lastMode = "md-mode-" + mode; + if ( lastMode ) container.addClass( lastMode ); + + break; + default: + container.addClass('ng-hide'); + if ( lastMode ) { + container.removeClass( lastMode ); + lastMode = undefined; + } + break; + } }); - } - function clamp(value) { - if (value > 100) { - return 100; + function mode() { + var value = attr.mdMode; + if ( value ) { + switch(value) { + case MODE_DETERMINATE: + case MODE_INDETERMINATE: + case MODE_BUFFER: + case MODE_QUERY: + break; + default: + value = undefined; + break; + } + } + return value; } - if (value < 0) { - return 0; - } + /** + * Manually set CSS to animate the Determinate indicator based on the specified + * percentage value (0-100). + */ + function animateIndicator(target, value) { + if ( !mode() ) return; - return Math.ceil(value || 0); + var to = $mdUtil.supplant("translateX({0}%) scale({1},1)", [ (value-100)/2, value/100 ]); + var styles = toVendorCSS({ transform : to }); + angular.element(target).css( styles ); + } } -} - -// ********************************************************** -// Private Methods -// ********************************************************** -var transforms = (function() { - var values = new Array(101); - for(var i = 0; i < 101; i++){ - values[i] = makeTransform(i); + /** + * Clamps the value to be between 0 and 100. + * @param {number} value The value to clamp. + * @returns {number} + */ + function clamp(value) { + return Math.max(0, Math.min(value || 0, 100)); } +} - return values; - - function makeTransform(value){ - var scale = value/100; - var translateX = (value-100)/2; - return 'translateX(' + translateX.toString() + '%) scale(' + scale.toString() + ', 1)'; - } -})(); diff --git a/src/components/progressLinear/progress-linear.scss b/src/components/progressLinear/progress-linear.scss index cfb1607f6fe..b2cb08776ee 100644 --- a/src/components/progressLinear/progress-linear.scss +++ b/src/components/progressLinear/progress-linear.scss @@ -1,92 +1,108 @@ $progress-linear-bar-height: 5px !default; -md-progress-linear:not([md-mode="indeterminate"]) { - display: block; +md-progress-linear { + display: inline-block; + position: relative; width: 100%; height: $progress-linear-bar-height; .md-container { - overflow: hidden; position: relative; + display:block; + overflow: hidden; + + width:100%; height: $progress-linear-bar-height; top: $progress-linear-bar-height; - transform: translate(0, 5px) scale(1, 0); - transition: all .3s linear; - } - .md-container.md-ready { - transform: translate(0, 0) scale(1, 1); - } - - .md-bar { - height: $progress-linear-bar-height; - position: absolute; - width: 100%; - } + transform: translate(0, 0) scale(1, 1);; - .md-bar1, .md-bar2 { - transition: all 0.2s linear; - } + .md-bar { + position: absolute; - &[md-mode=determinate] { - .md-bar1 { - display: none; - } - } + left: 0; + top: 0; + bottom: 0; - &[md-mode=buffer] { - .md-container { - background-color: transparent !important; + width: 100%; + height: $progress-linear-bar-height; } .md-dashed:before { content: ""; - display: block; + display: none; + position: absolute; + + margin-top: 0; height: $progress-linear-bar-height; width: 100%; - margin-top: 0; - position: absolute; + background-color: transparent; background-size: 10px 10px !important; background-position: 0px -23px; - animation: buffer 3s infinite linear; } - } - &[md-mode=query] { - .md-bar2 { - animation: query .8s infinite cubic-bezier(0.390, 0.575, 0.565, 1.000); + .md-bar1, .md-bar2 { + + // Just set the transition information here. + // Note: the actual transform values are calculated in JS + + transition: transform 0.2s linear; } - } -} -md-progress-linear[md-mode="indeterminate"] { - display: block; - width: 100%; - height: $progress-linear-bar-height; - position: relative; - .md-container { - width: 100%; - overflow: hidden; - position: relative; - height: $progress-linear-bar-height; - top: $progress-linear-bar-height; - transition: all .3s linear; - .md-bar { - height: $progress-linear-bar-height; - left: 0; - width: 288 * 100% / 360; - position: absolute; - top: 0; - bottom: 0; + // ************************************************************ + // Animations for modes: Determinate, InDeterminate, and Query + // ************************************************************ + + &.md-mode-query { + .md-bar1 { + display: none; + } + .md-bar2 { + transition: all 0.2s linear; + animation: query .8s infinite cubic-bezier(0.390, 0.575, 0.565, 1.000); + } + } + + &.md-mode-determinate { + .md-bar1 { + display: none; + } + } + + &.md-mode-indeterminate { + .md-bar1 { + animation: md-progress-linear-indeterminate-scale-1 4s infinite, + md-progress-linear-indeterminate-1 4s infinite; + } + .md-bar2 { + animation: md-progress-linear-indeterminate-scale-2 4s infinite, + md-progress-linear-indeterminate-2 4s infinite; + } } - .md-bar1 { - animation: md-progress-linear-indeterminate-scale-1 4s infinite, - md-progress-linear-indeterminate-1 4s infinite; + + &.ng-hide { + animation: none; + + .md-bar1 { + animation-name: none; + } + .md-bar2 { + animation-name: none; + } } - .md-bar2 { - animation: md-progress-linear-indeterminate-scale-2 4s infinite, - md-progress-linear-indeterminate-2 4s infinite; + } + + // Special animations for the `buffer` mode + + .md-container.md-mode-buffer { + background-color: transparent !important; + + transition: all 0.2s linear; + + .md-dashed:before { + display: block; + animation: buffer 3s infinite linear; } } } @@ -101,7 +117,6 @@ md-progress-linear[md-mode="indeterminate"] { transform: translateX(-50%) scale(0, 1); } } - @keyframes buffer { 0% { opacity: 1; @@ -115,7 +130,6 @@ md-progress-linear[md-mode="indeterminate"] { background-position: -200px -23px; } } - @keyframes md-progress-linear-indeterminate-scale-1 { 0% { transform: scaleX(0.1); @@ -133,7 +147,6 @@ md-progress-linear[md-mode="indeterminate"] { transform: scaleX(0.1); } } - @keyframes md-progress-linear-indeterminate-1 { 0% { left: -378.6 * 100% / 360; @@ -151,7 +164,6 @@ md-progress-linear[md-mode="indeterminate"] { left: 343.6 * 100% / 360; } } - @keyframes md-progress-linear-indeterminate-scale-2 { 0% { transform: scaleX(0.1); @@ -169,7 +181,6 @@ md-progress-linear[md-mode="indeterminate"] { transform: scaleX(0.1); } } - @keyframes md-progress-linear-indeterminate-2 { 0% { left: -197.6 * 100% / 360; @@ -187,3 +198,5 @@ md-progress-linear[md-mode="indeterminate"] { left: 422.6 * 100% / 360; } } + + diff --git a/src/components/select/select.js b/src/components/select/select.js index 3f204c92342..82126cd9a9b 100755 --- a/src/components/select/select.js +++ b/src/components/select/select.js @@ -97,12 +97,12 @@ function SelectDirective($mdSelect, $mdUtil, $mdTheming, $mdAria, $rootElement, if (attr.mdOnOpen) { // Show progress indicator while loading async + // Use ng-hide for `display:none` so the indicator does not interfere with the options list element .find('md-content') .prepend(angular.element( '
' + - ' ' + - ' ' + + ' ' + '
' )); @@ -988,10 +988,12 @@ function SelectProvider($$interimElementProvider) { function watchAsyncLoad() { if (opts.loadingAsync && !opts.isRemoved) { scope.$$loadingAsyncDone = false; + scope.progressMode = 'indeterminate'; $q.when(opts.loadingAsync) .then(function() { scope.$$loadingAsyncDone = true; + scope.progressMode = ''; delete opts.loadingAsync; }).then(function() { $$rAF(positionAndFocusMenu);