Skip to content

Commit de647fc

Browse files
committed
New implementation of LoadingIndicator
Used to be an animated GIF, that did not work well on non-white backgrounds; now it is an SVG, animated by JS, it should work fine on any background. Fixes #436
1 parent 4813f03 commit de647fc

File tree

6 files changed

+112
-18
lines changed

6 files changed

+112
-18
lines changed
Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`renders correctly 1`] = `
4-
<img
5-
alt="Loading..."
6-
className="src-shared-components-LoadingIndicator-___styles__style___3RNHL"
7-
src="/images/2f402d440d802a10b7672b75897c47a5.gif"
8-
/>
4+
<svg
5+
className="src-shared-components-LoadingIndicator-___styles__container___V7JxF"
6+
viewBox="0 0 64 64"
7+
>
8+
<circle
9+
className="src-shared-components-LoadingIndicator-___styles__circle1___hEgvY"
10+
cx="32"
11+
cy="32"
12+
r="28"
13+
/>
14+
<circle
15+
className="src-shared-components-LoadingIndicator-___styles__circle2___2-o58"
16+
cx="32"
17+
cy="32"
18+
r="6"
19+
/>
20+
</svg>
921
`;

__tests__/shared/components/tc-communities/__snapshots__/JoinCommunity.jsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ exports[`Matches shallow shapshot 2`] = `
3535
mapThemrProps={[Function]}
3636
theme={
3737
Object {
38-
"style": "src-shared-components-tc-communities-JoinCommunity-___style__loadingIndicator___2RP1u",
38+
"container": "src-shared-components-tc-communities-JoinCommunity-___style__loadingIndicator___2RP1u",
3939
}
4040
}
4141
themePriority="adhoc-context-default"

config/jest/setup.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* global Event, jest */
22

3+
import _ from 'lodash';
4+
35
global.window.matchMedia = global.window.matchMedia || function matchMedia() {
46
return {
57
matches: false,
@@ -8,6 +10,8 @@ global.window.matchMedia = global.window.matchMedia || function matchMedia() {
810
};
911
};
1012

13+
global.window.requestAnimationFrame = _.noop;
14+
1115
global.window.resizeTo = (width, height) => {
1216
global.window.innerWidth = width || global.window.innerWidth;
1317
global.window.innerHeight = height || global.window.innerHeight;

src/shared/components/LoadingIndicator/index.jsx

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,81 @@
1+
/**
2+
* Waiting indicator: two co-centric circles periodically increasing their
3+
* radiuses till maximum value, then resetting it to zero.
4+
*
5+
* NOTE: It is implemented as class component because stand-alone animated SVG
6+
* images are not well supported across all browsers yet.
7+
*/
8+
9+
/* global window */
10+
111
import PT from 'prop-types';
2-
import Source from 'assets/images/ripple.gif';
312
import React from 'react';
413
import { themr } from 'react-css-super-themr';
514
import style from './styles.scss';
615

7-
function LoadingIndicator({ theme }) {
8-
return (
9-
<img
10-
alt="Loading..."
11-
className={theme.style}
12-
src={Source}
13-
/>
14-
);
16+
class LoadingIndicator extends React.Component {
17+
/**
18+
* Calculates animation phase for the specified timestamp.
19+
* @param {Number} timestamp Current animation time in [ms].
20+
* @param {Number} shift Optional phase shift [dimensionless].
21+
* @return {Number} Returns animation phase: a number in [0.0; 1.0] range,
22+
* specifying current position inside animation loop. You may note that
23+
* the result does not change linearly with time, instead it goes quadratic
24+
* in a way that makes animation progress faster in the beginning, and
25+
* slower in the end of a cycle.
26+
*/
27+
static calcPhase(timestamp, shift = 0) {
28+
const PERIOD = 2; /* [s] */
29+
return ((timestamp / 1000 / PERIOD) + shift) % 1;
30+
}
31+
32+
componentDidMount() {
33+
this.animationId = window.requestAnimationFrame(ts => this.animation(ts));
34+
}
35+
36+
componentWillUnmount() {
37+
window.cancelAnimationFrame(this.animationId);
38+
}
39+
40+
/* Moves animation forward. */
41+
animation(timestamp) {
42+
if (this.circle1) {
43+
const phase1 = LoadingIndicator.calcPhase(timestamp);
44+
this.circle1.setAttribute('r', 28 * phase1 * (2.0 - phase1));
45+
this.circle1.setAttribute('opacity', 1.0 - (phase1 * phase1));
46+
}
47+
if (this.circle2) {
48+
const phase2 = LoadingIndicator.calcPhase(timestamp, 0.5);
49+
this.circle2.setAttribute('r', 28 * phase2 * (2.0 - phase2));
50+
this.circle2.setAttribute('opacity', 1.0 - (phase2 * phase2));
51+
}
52+
this.animationId = window.requestAnimationFrame(ts => this.animation(ts));
53+
}
54+
55+
render() {
56+
const { theme } = this.props;
57+
return (
58+
<svg
59+
className={theme.container}
60+
viewBox="0 0 64 64"
61+
>
62+
<circle
63+
className={theme.circle1}
64+
cx="32"
65+
cy="32"
66+
r="28"
67+
ref={(node) => { this.circle1 = node; }}
68+
/>
69+
<circle
70+
className={theme.circle2}
71+
cx="32"
72+
cy="32"
73+
r="6"
74+
ref={(node) => { this.circle2 = node; }}
75+
/>
76+
</svg>
77+
);
78+
}
1579
}
1680

1781
LoadingIndicator.propTypes = {
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
@import '~styles/tc-styles';
1+
@import '~styles/mixins';
22

3-
.style {
3+
.container {
44
display: block;
5+
height: 64px;
56
margin: 0 auto;
67
text-align: center;
8+
width: 64px;
9+
}
10+
11+
.circle1 {
12+
fill: none;
13+
stroke: #149efe;
14+
stroke-width: 2;
15+
}
16+
17+
.circle2 {
18+
fill: none;
19+
stroke: #e3e4e5;
20+
stroke-width: 2;
721
}

src/shared/components/tc-communities/JoinCommunity/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function JoinCommunity({
5353
{ state === STATE.JOINING ? (
5454
<div>
5555
<p>Joining...</p>
56-
<LoadingIndicator theme={{ style: style.loadingIndicator }} />
56+
<LoadingIndicator theme={{ container: style.loadingIndicator }} />
5757
</div>
5858
) : label}
5959
</button>

0 commit comments

Comments
 (0)