Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

Commit

Permalink
fix(progressCircular): css reduction with js animations
Browse files Browse the repository at this point in the history
reduce the generated css by removing the scss for loops for animations of determinate indicators; use JS animations.

*  enable animation disables simply by setting md-mode = ""
*  hide div.md-spinner-wrapper when the md-mode is empty or not valid
*  use class selectors instead of attribute selectors (ie perfs)
*  create `animateIndicator()` for determinate indicators
*  remove SCSS for loops for css generation; ~54k reduction in CSS size

BREAKING-CHANGES

*  ProgressCircular now uses Class selectors instead of `value` Attribute selectors.
*  `Determinate` animations are now JS based to set the CSS programmatically.

Before:

```css
md-progress-circular[value="1"] .md-inner .md-left .md-half-circle {
  -webkit-transform: rotate(135deg);
  transform: rotate(135deg);
 }
md-progress-circular[value="1"] .md-inner .md-right .md-half-circle {
 transition: -webkit-transform 0.1s linear;
 transition: transform 0.1s linear;
 -webkit-transform: rotate(-131.4deg);
 transform: rotate(-131.4deg);
 }

 md-progress-circular[md-mode=indeterminate] .md-spinner-wrapper {
   -webkit-animation: outer-rotate 2.91667s linear infinite;
 }
 ```

 Now:

 ```css
// [Value]-based animation CSS generated in JS

md-progress-circular.md-mode-indeterminate .md-spinner-wrapper {
  -webkit-animation: outer-rotate 2.91667s linear infinite;
}
 ```
  • Loading branch information
ThomasBurleson committed Sep 1, 2015
1 parent 303ab0d commit b1f7dc4
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 121 deletions.
25 changes: 12 additions & 13 deletions src/components/progressCircular/demoBasicUsage/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,27 @@ <h4>Indeterminate</h4>

<h4>Theming </h4>

<p>Use `md-warn` or `md-accent` to apply theme colors to your loader.</p>
<p>Your current theme colors can be used to easily colorize your progress indicator with `md-warn` or `md-accent` colors:</p>
<div layout="row" layout-sm="column" layout-align="space-around" >
<md-progress-circular class="md-hue-2" md-mode="{{vm.modes[0]}}" md-diameter="20px" ></md-progress-circular>
<md-progress-circular class="md-accent" md-mode="{{vm.modes[1]}}" md-diameter="40" ></md-progress-circular>
<md-progress-circular class="md-accent md-hue-1" md-mode="{{vm.modes[2]}}" md-diameter="60" ></md-progress-circular>
<md-progress-circular class="md-accent md-hue-1" md-mode="{{vm.modes[2]}}" md-diameter="60" ></md-progress-circular>
<md-progress-circular class="md-warn md-hue-3" md-mode="{{vm.modes[3]}}" md-diameter="70"></md-progress-circular>
<md-progress-circular md-mode="{{vm.modes[4]}}" md-diameter="96"></md-progress-circular>
</div>

<div layout="row" id="loaders">
<div layout="row" id="loaders">

<p style="margin-right: 20px">Show Progress Circular Indicators: </p>
<p style="margin-right: 20px">Show Progress Circular Indicators: </p>

<h5>Off</h5>
<md-switch
ng-model="vm.activated"
ng-change="vm.toggleActivation()"
style=""
aria-label="Toggle Progress Circular Indicators">
<h5>On</h5>
</md-switch>
</div>
<h5>Off</h5>
<md-switch
ng-model="vm.activated"
ng-change="vm.toggleActivation()"
aria-label="Toggle Progress Circular Indicators">
<h5>On</h5>
</md-switch>
</div>


</div>
7 changes: 3 additions & 4 deletions src/components/progressCircular/demoBasicUsage/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ angular
function($scope, $interval) {
var self = this, j= 0, counter = 0;

self.mode = 'query';
self.modes = new Array(4);
self.modes = [ ];
self.activated = true;
self.determinateValue = 30;

/**
* Turn off or on the 5 themed loaders
*/
self.toggleActivation = function() {
if ( !self.activated ) self.modes = [ ];
if ( self.activated ) j = counter = 0;
if ( !self.activated ) self.modes = [ ];
if ( self.activated ) j = counter = 0;
};

// Iterate every 100ms, non-stop
Expand Down
85 changes: 79 additions & 6 deletions src/components/progressCircular/progress-circular.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,17 @@ angular.module('material.components.progressCircular', [
* <md-progress-circular md-mode="indeterminate"></md-progress-circular>
* </hljs>
*/
function MdProgressCircularDirective($mdConstant, $mdTheming) {
function MdProgressCircularDirective($mdConstant, $mdTheming, $mdUtil) {
var DEFAULT_PROGRESS_SIZE = 100;
var DEFAULT_SCALING = 0.5;

var MODE_DETERMINATE = "determinate",
MODE_INDETERMINATE = "indeterminate";


return {
restrict: 'E',
scope : true,
template:
// The progress 'circle' is composed of two half-circles: the left side and the right
// side. Each side has CSS applied to 'fill-in' the half-circle to the appropriate progress.
Expand Down Expand Up @@ -79,19 +84,79 @@ function MdProgressCircularDirective($mdConstant, $mdTheming) {

function postLink(scope, element, attr) {
$mdTheming(element);

var circle = element[0];
var spinnerWrapper = angular.element(element.children()[0]);

var lastMode, toVendorCSS = $mdUtil.dom.animator.toCss;

// Update size/scaling of the progress indicator
// Watch the "value" and "md-mode" attributes

circle.style[$mdConstant.CSS.TRANSFORM] = 'scale(' + getDiameterRatio() + ')';

attr.$observe('value', function(value) {
var percentValue = clamp(value);
element.attr('aria-valuenow', percentValue);

if (attr.mdMode == "determinate") {
animateIndicator(percentValue);
}
});

var spinnerWrapper = angular.element(element.children()[0]);
attr.$observe('mdMode',function(mode){
spinnerWrapper[mode ? 'removeClass' : 'addClass']('ng-hide');
switch( mode ) {
case MODE_DETERMINATE:
case MODE_INDETERMINATE:
spinnerWrapper.removeClass('ng-hide');

// Inject class selector instead of attribute selector
// (@see layout.js changes for IE performance issues)

if ( lastMode ) spinnerWrapper.removeClass( lastMode );
lastMode = "md-mode-" + mode;
if ( lastMode ) spinnerWrapper.addClass( lastMode );

break;
default:
spinnerWrapper.addClass('ng-hide');
}
});

var leftC, rightC, gap;

/**
* Manually animate the Determinate indicator based on the specified
* percentage value (0-100).
*
* Note: this animation was previously done using SCSS.
* - generated 54K of styles
* - use attribute selectors which had poor performances in IE
*/
function animateIndicator(value) {
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'));

var gapStyles = removeEmptyValues({
borderBottomColor: (value <= 50) ? "transparent !important" : "",
transition: (value <= 50) ? "" : "borderBottomColor 0.1s linear"
}),
leftStyles = removeEmptyValues({
transition: (value <= 50) ? "transform 0.1s linear" : "",
transform: $mdUtil.supplant("rotate({0}deg)", [value <= 50 ? 135 : ((((value - 50) / 50) * 180) + 135)])
}),
rightStyles = removeEmptyValues({
transition: (value >= 50) ? "transform 0.1s linear" : "",
transform: $mdUtil.supplant("rotate({0}deg)", [value >= 50 ? 45 : (value / 50 * 180 - 135)])
});

leftC.css(toVendorCSS(leftStyles));
rightC.css(toVendorCSS(rightStyles));
gap.css(toVendorCSS(gapStyles));

}

/**
* We will scale the progress circle based on the default diameter.
*
Expand All @@ -102,9 +167,7 @@ function MdProgressCircularDirective($mdConstant, $mdTheming) {
if ( !attr.mdDiameter ) return DEFAULT_SCALING;

var match = /([0-9]*)%/.exec(attr.mdDiameter);
var value = match && match[1]/100;

value = Math.max(0, value || parseFloat(attr.mdDiameter));
var value = Math.max(0, (match && match[1]/100) || parseFloat(attr.mdDiameter));

// should return ratio; DEFAULT_PROGRESS_SIZE === 100px is default size
return (value > 1) ? value / DEFAULT_PROGRESS_SIZE : value;
Expand All @@ -119,4 +182,14 @@ function MdProgressCircularDirective($mdConstant, $mdTheming) {
function clamp(value) {
return Math.max(0, Math.min(value || 0, 100));
}

function removeEmptyValues(target) {
for (var key in target) {
if (target.hasOwnProperty(key)) {
if ( target[key] == "" ) delete target[key];
}
}

return target;
}
}
155 changes: 57 additions & 98 deletions src/components/progressCircular/progress-circular.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,6 @@ $progress-circular-sporadic-duration : $progress-circular-duration !default;
$progress-border-width : 10px;
$progress-circular-size : 10 * $progress-border-width !default;

@keyframes outer-rotate {
100% { transform: rotate(360deg); }
}
@keyframes left-wobble {
0%, 100% { transform: rotate(130deg); }
50% { transform: rotate( -5deg); }
}
@keyframes right-wobble {
0%, 100% { transform: rotate(-130deg); }
50% { transform: rotate( 5deg); }
}
@keyframes sporadic-rotate {
12.5% { transform: rotate( 135deg); }
25% { transform: rotate( 270deg); }
37.5% { transform: rotate( 405deg); }
50% { transform: rotate( 540deg); }
62.5% { transform: rotate( 675deg); }
75% { transform: rotate( 810deg); }
87.5% { transform: rotate( 945deg); }
100% { transform: rotate(1080deg); }
}

md-progress-circular {
width: $progress-circular-size;
height: $progress-circular-size;
Expand Down Expand Up @@ -86,79 +64,32 @@ md-progress-circular {
}
}

// TODO: This while-loop generates about 2 kilobytes of css after gzip.
// Refactor pr ogressCircular to animate with javascript.
$i: 0;
@while $i <= 100 {
&[value="#{$i}"] {
.md-inner {
.md-left {
.md-half-circle {
@if $i < 50 {
transform: rotate(135deg);
} @else {
transition: transform 0.1s linear;
$deg: ((($i - 50) / 50) * 180) + 135;
transform: rotate(#{$deg}deg);
}
}
}
.md-right {
.md-half-circle {
@if $i <= 50 {
transition: transform 0.1s linear;
$deg: $i / 50 * 180 - 135;
transform: rotate(#{$deg}deg);
} @else {
transform: rotate(45deg);
}
}
}
.md-gap {
border-bottom-width: $progress-border-width;
border-bottom-style: solid;
@if $i <= 50 {
border-bottom-color: transparent !important;
} @else {
transition: border-bottom-color 0.1s linear;
}
.md-spinner-wrapper.md-mode-indeterminate {
animation: outer-rotate $progress-circular-outer-duration linear infinite;
.md-inner {
animation: sporadic-rotate $progress-circular-sporadic-duration $progress-circular-ease-in-out infinite;
.md-left, .md-right {
.md-half-circle {
animation-iteration-count: infinite;
animation-duration: ($progress-circular-duration * 0.25);
animation-timing-function: $progress-circular-ease-in-out;
}
}
}
$i: $i + 1;
}

&[md-mode=indeterminate], .md-mode-indeterminate {
.md-spinner-wrapper {
animation: outer-rotate $progress-circular-outer-duration linear infinite;
.md-inner {
animation: sporadic-rotate $progress-circular-sporadic-duration $progress-circular-ease-in-out infinite;
.md-left, .md-right {
.md-half-circle {
animation-iteration-count: infinite;
animation-duration: ($progress-circular-duration * 0.25);
animation-timing-function: $progress-circular-ease-in-out;
}
}
.md-left {
.md-half-circle {
animation-name: left-wobble;
}
.md-left {
.md-half-circle {
animation-name: left-wobble;
}
.md-right {
.md-half-circle {
animation-name: right-wobble;
}
}
.md-right {
.md-half-circle {
animation-name: right-wobble;
}
}
}
}
}

.ng-hide md-progress-circular,
md-progress-circular.ng-hide, {
&[md-mode=indeterminate], .md-mode-indeterminate {
.md-spinner-wrapper {
.ng-hide md-progress-circular, md-progress-circular.ng-hide {
.md-spinner-wrapper {
animation: none;
.md-inner {
animation: none;
Expand All @@ -175,21 +106,49 @@ md-progress-circular.ng-hide, {
}
}
}
}

.md-spinner-wrapper.ng-hide {
animation: none;
.md-inner {
.md-spinner-wrapper.ng-hide {
animation: none;
.md-left {
.md-half-circle {
animation-name: none;
.md-inner {
animation: none;
.md-left {
.md-half-circle {
animation-name: none;
}
}
}
.md-right {
.md-half-circle {
animation-name: none;
.md-right {
.md-half-circle {
animation-name: none;
}
}
}
}

}


//
// Keyframe animation for the Indeterminate Progress
//
@keyframes outer-rotate {
100% { transform: rotate(360deg); }
}
@keyframes left-wobble {
0%, 100% { transform: rotate(130deg); }
50% { transform: rotate( -5deg); }
}
@keyframes right-wobble {
0%, 100% { transform: rotate(-130deg); }
50% { transform: rotate( 5deg); }
}
@keyframes sporadic-rotate {
12.5% { transform: rotate( 135deg); }
25% { transform: rotate( 270deg); }
37.5% { transform: rotate( 405deg); }
50% { transform: rotate( 540deg); }
62.5% { transform: rotate( 675deg); }
75% { transform: rotate( 810deg); }
87.5% { transform: rotate( 945deg); }
100% { transform: rotate(1080deg); }
}

3 changes: 3 additions & 0 deletions src/core/util/animation/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ function AnimateDomUtils($mdUtil, $q, $timeout, $mdConstant, $animateCss) {
css[key] = value + 'px';
} else {
switch (key) {
case 'transition':
convertToVendor(key, $mdConstant.CSS.TRANSITION, value);
break;
case 'transform':
convertToVendor(key, $mdConstant.CSS.TRANSFORM, value);
break;
Expand Down

0 comments on commit b1f7dc4

Please sign in to comment.