<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@15.7.0/dist/react.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@15.7.0/dist/react-dom.min.js" crossorigin></script>
</body>
</html>
* {
box-sizing: border-box;
}
body {
margin: 0 auto;
width: 80%;
height: 80%;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.root {
display: inline-block;
}
.progressPath {
stroke-dasharray: 60, 310;
will-change: stroke, stroke-dashoffset;
}
function lerp(x, x0, x1, y0, y1) {
const t = (x - x0) / (x1 - x0);
return y0 + t * (y1 - y0);
}
function lerpColor(x, x0, x1, y0, y1) {
const b0 = y0 & 0xff;
const g0 = (y0 & 0xff00) >> 8;
const r0 = (y0 & 0xff0000) >> 16;
const b1 = y1 & 0xff;
const g1 = (y1 & 0xff00) >> 8;
const r1 = (y1 & 0xff0000) >> 16;
const r = Math.floor(lerp(x, x0, x1, r0, r1));
const g = Math.floor(lerp(x, x0, x1, g0, g1));
const b = Math.floor(lerp(x, x0, x1, b0, b1));
return "#" + ("00000" + ((r << 16) | (g << 8) | b).toString(16)).slice(-6);
}
function lerpTable(vIndex, tValue, table, canExtrapolate, lerpFunc = lerp) {
const rowCount = table.length;
for (let i = 0; i < rowCount; ++i) {
let a = table[i][0];
if (i === 0 && tValue < a) {
if (canExtrapolate) {
return lerpFunc(
tValue,
a,
table[i + 1][0],
table[i][vIndex],
table[i + 1][vIndex]
);
}
return table[i][vIndex];
}
if (i === rowCount - 1 && tValue >= a) {
if (canExtrapolate) {
return lerpFunc(
tValue,
table[i - 1][0],
a,
table[i - 1][vIndex],
table[i][vIndex]
);
}
return table[i][vIndex];
}
if (tValue >= a && tValue <= table[i + 1][0]) {
return lerpFunc(
tValue,
a,
table[i + 1][0],
table[i][vIndex],
table[i + 1][vIndex]
);
}
}
return 0;
}
function animate() {
let pathWidth = 372;
let speed = 2;
const colorTable = [
// ease in
[0.0, 0xf15a31],
[0.2, 0xffd31b],
[0.33, 0xa6ce42],
[0.4, 0x007ac1],
[0.45, 0x007ac1],
// ease out
[0.55, 0x007ac1],
[0.6, 0x007ac1],
[0.67, 0xa6ce42],
[0.8, 0xffd31b],
[1.0, 0xf15a31]
];
let currentAnim = Date.now();
let offset = this.state.offset;
let t = ((currentAnim - this._animStart) % 6000) / 6000; // we want 6secs for color animation cycle
let colorValue = lerpTable(1, t, colorTable, false, lerpColor);
offset -= speed;
if (offset < 0) offset = pathWidth;
this.setState({
stroke: colorValue,
offset: offset
});
this._animId =
window.requestAnimationFrame &&
window.requestAnimationFrame(animate.bind(this));
return this._animId;
}
function start() {
this._animStart = Date.now();
this._animId = animate.call(this);
}
function stop() {
if (this._animId) {
window.cancelAnimationFrame && window.cancelAnimationFrame(this._animId);
this._animId = null;
}
}
class Spinner extends React.Component {
constructor() {
super(...arguments);
this.state = {
running: false,
stroke: "#ededed",
offset: 445
};
}
componentDidMount() {
start.call(this);
}
componentWillUnmount() {
stop.call(this);
}
render() {
let { offset, stroke } = this.state;
let pathStyle = {
stroke: stroke,
strokeDashoffset: offset
};
return React.createElement(
"svg",
{
height: "100vh",
viewBox: "0 0 115 115",
preserveAspectRatio: "xMidYMid meet",
className: "root"
},
React.createElement("path", {
opacity: "0.05",
fill: "none",
stroke: "#000000",
strokeWidth: "3",
d:
"M 85 85 C -5 16 -39 127 78 30 C 126 -9 57 -16 85 85 C 94 123 124 111 85 85 Z"
}),
React.createElement("path", {
style: pathStyle,
className: "progressPath",
fill: "none",
strokeWidth: "3",
strokeLinecap: "round",
d:
"M 85 85 C -5 16 -39 127 78 30 C 126 -9 57 -16 85 85 C 94 123 124 111 85 85 Z"
})
);
}
}
ReactDOM.render(React.createElement(Spinner), document.getElementById("root"));
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.