Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions packages/loaders/src/Dots.example.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const Color = ({ name, color, includeSample }) =>
<State
initialState={{
size: 48,
velocity: 0.05,
duration: 1250,
color: 'BLUE-500'
}}
>
Expand All @@ -94,13 +94,13 @@ const Color = ({ name, color, includeSample }) =>
</Col>
<Col md={6}>
<RangeField>
<Label>Velocity {state.velocity}</Label>
<Label>Duration {state.duration}</Label>
<Range
value={state.velocity}
onChange={event => setState({ velocity: parseFloat(event.target.value) })}
min={-0.5}
max={1}
step={0.05}
value={state.duration}
onChange={event => setState({ duration: parseFloat(event.target.value) })}
min={625}
max={2500}
step={625}
/>
</RangeField>
</Col>
Expand All @@ -125,7 +125,7 @@ const Color = ({ name, color, includeSample }) =>
</SpacedRow>
<SpacedRow>
<Col style={{ textAlign: 'center' }}>
<Dots size={`${state.size}px`} velocity={state.velocity} color={colors[state.color]} />
<Dots size={`${state.size}px`} duration={state.duration} color={colors[state.color]} />
</Col>
</SpacedRow>
</Grid>
Expand Down
81 changes: 31 additions & 50 deletions packages/loaders/src/Dots.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import React from 'react';
import PropTypes from 'prop-types';

import { retrieveXCoordinate, retrieveYCoordinate, KEYFRAME_MAX } from './utils/dot-coordinates';
import { DOT_ONE_FRAMES, DOT_TWO_FRAMES, DOT_THREE_FRAMES } from './utils/dot-coordinates';
import { DotsCircle, StyledSVG } from './styled-elements';
import ScheduleContainer from './containers/ScheduleContainer';

Expand All @@ -21,10 +21,9 @@ export default class Dots extends React.Component {
**/
size: PropTypes.any,
/**
* Velocity (speed) of the animation. Between -1 and 1.
* This should only be maniuplated at extreme sizes.
* Duration (ms) of the animation. Default is 1250ms.
**/
velocity: PropTypes.number,
duration: PropTypes.number,
/**
* Color of the loader. Can inherit from `color` styling.
**/
Expand All @@ -39,46 +38,40 @@ export default class Dots extends React.Component {
static defaultProps = {
size: 'inherit',
color: 'inherit',
velocity: 0.05,
delayMS: 750
delayMS: 750,
duration: 1250
};

state = {
frame: 0,
timestamp: 0
};

performAnimationFrame = (timestamp = 0) => {
const { velocity } = this.props;

let pinnedVelocity = velocity;

if (velocity < -1) {
pinnedVelocity = -0.9;
} else if (velocity > 1) {
pinnedVelocity = 1;
}
constructor(props) {
super(props);

this.setState(prevState => {
const factor = 1000 + 1000 * pinnedVelocity;
const elapsed = (timestamp - prevState.timestamp) / factor;
const frame = prevState.frame + (elapsed % KEYFRAME_MAX);

return { frame, timestamp };
});
};
this.state = {
frame: 0,
timestamp: 0,
rawFrame: 0,
totalFrames: 100
};
}

retrieveFrame = offset => {
const loop = KEYFRAME_MAX * 2;
performAnimationFrame = (timestamp = 0) => {
const { duration } = this.props;
const { totalFrames, rawFrame } = this.state;
const elapsedTime = timestamp - this.state.timestamp;
const frameMultiplier = (totalFrames + 1) / duration;
const nextValue = rawFrame + elapsedTime * frameMultiplier;
const actualFrame = Math.floor(nextValue);
const frame = actualFrame % totalFrames;
const currentRawFrame = nextValue % totalFrames;

return (this.state.frame + offset * loop) % loop;
this.setState({ timestamp, frame, rawFrame: currentRawFrame });
};

render() {
const { size, color, delayMS, ...other } = this.props;
const dotOneFrame = this.retrieveFrame(0);
const dotTwoFrame = this.retrieveFrame(1 / 3);
const dotThreeFrame = this.retrieveFrame(2 / 3);
const { frame } = this.state;
const [dotOneX, dotOneY] = DOT_ONE_FRAMES[frame];
const [dotTwoX, dotTwoY] = DOT_TWO_FRAMES[frame];
const [dotThreeX, dotThreeY] = DOT_THREE_FRAMES[frame];

return (
<ScheduleContainer tick={this.performAnimationFrame} size={size} delayMS={delayMS}>
Expand All @@ -92,21 +85,9 @@ export default class Dots extends React.Component {
{...other}
>
<g fill="currentColor">
<DotsCircle
transform={`translate(${retrieveXCoordinate(dotOneFrame)} ${retrieveYCoordinate(
dotOneFrame
)})`}
/>
<DotsCircle
transform={`translate(${retrieveXCoordinate(dotTwoFrame)} ${retrieveYCoordinate(
dotTwoFrame
)})`}
/>
<DotsCircle
transform={`translate(${retrieveXCoordinate(dotThreeFrame)} ${retrieveYCoordinate(
dotThreeFrame
)})`}
/>
<DotsCircle transform={`translate(${dotOneX} ${dotOneY})`} />
<DotsCircle transform={`translate(${dotTwoX} ${dotTwoY})`} />
<DotsCircle transform={`translate(${dotThreeX} ${dotThreeY})`} />
</g>
</StyledSVG>
)}
Expand Down
55 changes: 9 additions & 46 deletions packages/loaders/src/Spinner.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ import ScheduleContainer from './containers/ScheduleContainer';
const COMPONENT_ID = 'loaders.spinner';

export default class Spinner extends React.Component {
constructor(props) {
super(props);

this.strokeWidthValues = this.computeFrames(STROKE_WIDTH_FRAMES);
this.rotationValues = this.computeFrames(ROTATION_FRAMES);
this.dasharrayValues = this.computeFrames(DASHARRAY_FRAMES);
}

static propTypes = {
/**
* Size of the loader. Can inherit from `font-size` styling.
Expand Down Expand Up @@ -62,54 +54,25 @@ export default class Spinner extends React.Component {
timestamp: 0
};

computeFrames = frames => {
const { duration } = this.props;
const { totalFrames } = this.state;

return Object.entries(frames).reduce((acc, item, index, arr) => {
const [frame, value] = item;
const [nextFrame, nextValue] = arr[index + 1] || [totalFrames, arr[0][1]];
const diff = nextFrame - frame;
const frameHz = 1000 / 60;

let subDuration = (duration / (totalFrames - 1)) * diff;
let lastValue = value;

acc[frame] = value;
for (let idx = 0; idx < diff; idx++) {
lastValue = lastValue + (nextValue - lastValue) * (frameHz / subDuration);
subDuration = (duration / (totalFrames - 1)) * (diff - idx);

acc[parseInt(frame, 10) + idx + 1] = lastValue;
}
acc[nextFrame] = nextValue;

return acc;
}, {});
};

performAnimationFrame = (nowTime = 0) => {
const { totalFrames, rawFrame, timestamp } = this.state;
const { duration } = this.props;
const elapsedTime = nowTime - timestamp;
const frameMultiplier = (totalFrames + 1) / duration;
const nextValue = rawFrame + elapsedTime * frameMultiplier;
const actualFrame = Math.floor(nextValue);
const frame = actualFrame % totalFrames;
const currentRawFrame = nextValue % totalFrames;

this.setState(() => {
const frameMultiplier = (totalFrames + 1) / duration;
const nextValue = rawFrame + elapsedTime * frameMultiplier;
const actualFrame = Math.floor(nextValue);
const frame = actualFrame % totalFrames;
const currentRawFrame = nextValue % totalFrames;

return { frame, rawFrame: currentRawFrame, timestamp: nowTime };
});
this.setState({ frame, rawFrame: currentRawFrame, timestamp: nowTime });
};

render() {
const { size, color, delayMS, ...other } = this.props;
const { frame } = this.state;
const strokeWidthValue = this.strokeWidthValues[frame];
const rotationValue = this.rotationValues[frame];
const dasharrayValue = this.dasharrayValues[frame];
const strokeWidthValue = STROKE_WIDTH_FRAMES[frame];
const rotationValue = ROTATION_FRAMES[frame];
const dasharrayValue = DASHARRAY_FRAMES[frame];
const WIDTH = 80;
const HEIGHT = 80;

Expand Down
69 changes: 50 additions & 19 deletions packages/loaders/src/utils/animations.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,59 @@
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/

/**
* Accelerating from zero velocity
* @param {Number} time Time
*/
export function easeInCubic(time) {
return time * time * time;
}
const X_DISTANCE = 31;
const Y_DISTANCE = 27;
const isArray = arr => Array.isArray(arr);

/**
* Decelerating to zero velocity
* @param {Number} time Time
* Linear interpolation between sample frames provided
* @param {Object} frames - The frame samples to compute from
* @param {Object} opts - Options to change computed values
* @param {Number} opts.duration - Duration of animation in ms
* @param {Number} opts.totalFrames - Frames to generate from sample
* @param {Number} opts.factor - Factor to increase amout of x coordinate
*/
export function easeOutCubic(time) {
const value = time - 1;
export function computeFrames(frames, opts = {}) {
const { duration = 1250, totalFrames = 100, factor = 0 } = opts;
const movex = factor * X_DISTANCE;
const movey = Y_DISTANCE;

return value * value * value + 1;
}
return Object.entries(frames).reduce((acc, item, index, arr) => {
const [frame, value] = item;
const [x, y] = isArray(value) ? value : [];
const [nextFrame, nextValue] = arr[index + 1] || [totalFrames, arr[0][1]];
const [xNext, yNext] = isArray(nextValue) ? nextValue : [];
const diff = nextFrame - frame;
const frameHz = 1000 / 60;

/**
* Acceleration until halfway, then deceleration
* @param {Number} time Time
*/
export function easeInOutCubic(time) {
return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1;
let subDuration = (duration / totalFrames) * diff;
let lastValue = value;
const [xLast, yLast] = isArray(lastValue) ? lastValue : [];

acc[frame] = isArray(value) ? [x + movex, y + movey] : value + movex;
// Compute the linear interpolation between current and next frame
for (let idx = 0; idx < diff; idx++) {
if (isArray(lastValue)) {
lastValue = [
xLast + (xNext - xLast) * (frameHz / subDuration),
yLast + (yNext - yLast) * (frameHz / subDuration)
];
} else {
lastValue =
lastValue +
(isArray(nextValue) ? xNext - lastValue : nextValue - lastValue) *
(frameHz / subDuration);
}

subDuration = (duration / totalFrames) * (diff - idx);

acc[parseInt(frame, 10) + idx + 1] = isArray(lastValue)
? [xLast + movex, yLast + movey]
: lastValue + movex;
}

acc[nextFrame] = isArray(nextValue) ? [xNext + movex, yNext + movey] : nextValue + movex;

return acc;
}, {});
}
29 changes: 10 additions & 19 deletions packages/loaders/src/utils/animations.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,19 @@
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/

import { easeInCubic, easeOutCubic, easeInOutCubic } from './animations';
import { computeFrames } from './animations';

describe('animations', () => {
describe('easeInCubic()', () => {
it('returns correct value based on time', () => {
expect(easeInCubic(2)).toBe(8);
});
});

describe('easeOutCubic()', () => {
it('returns correct value based on time', () => {
expect(easeOutCubic(2)).toBe(2);
});
});

describe('easeInOutCubic()', () => {
it('returns correct value when time is less than 0.5', () => {
expect(easeInOutCubic(0.45)).toBeCloseTo(0.3645);
});
describe('computeFrames', () => {
it('linear interpolation between frames', () => {
const { 0: first, 50: middle, 100: last } = computeFrames(
{ 0: 1, 99: 100 },
{ duration: 1000, totalFrames: 100 }
);

it('returns correct value when time is greater than 0.5', () => {
expect(easeInOutCubic(3)).toBe(33);
expect(first).toBe(1);
expect(last).toBe(1);
expect(middle).toBe(68.99705398202416);
});
});
});
Loading