body, html {
margin: 0;
padding: 0;
}
.input-range-rv {
vertical-align: middle;
background-color: transparent;
padding-top: 8px;
padding-top: .5rem;
padding-bottom: 8px;
padding-bottom: .5rem;
color: inherit;
background-color: transparent;
-webkit-appearance: none;
}
.input-range-rv::-webkit-slider-thumb {
position: relative;
width: 8px;
width: .5rem;
height: 20px;
height: 1.25rem;
cursor: pointer;
margin-top: -8px;
margin-top: -0.5rem;
border-radius: 3px;
background-color: #fff;
-webkit-appearance: none;
}
/* Touch screen friendly pseudo element */
.input-range-rv::-webkit-slider-thumb:before {
content: '';
display: block;
position: absolute;
top: -8px;
top: -0.5rem;
left: -14px;
left: -0.875rem;
width: 36px;
width: 2.25rem;
height: 36px;
height: 2.25rem;
opacity: 0;
}
.input-range-rv::-moz-range-thumb {
width: 8px;
width: .5rem;
height: 20px;
height: 1.25rem;
cursor: pointer;
border-radius: 3px;
border-color: transparent;
border-width: 0;
background-color: #fff;
}
.input-range-rv::-webkit-slider-runnable-track {
height: 4px;
height: 0.25rem;
cursor: pointer;
border-radius: 3px;
background-color: rgba(255, 255, 255, .25);
}
.input-range-rv::-moz-range-track {
height: 4px;
height: 0.25rem;
cursor: pointer;
border-radius: 3px;
background-color: rgba(255, 255, 255, .25);
}
.input-range-rv:focus {
outline: none;
}
const degreesToRadians = angle => (Math.PI * angle) / 180;
const range = count => Array.from(Array(count).keys());
function polygon(sideCount, radius) {
const theta = 360 / sideCount;
const vertexIndices = range(sideCount);
return vertexIndices.map(idx => ({
theta: degreesToRadians(theta * idx),
r: radius,
}));
}
const pathDef = vertices =>
`M${vertices.map(({ theta, r }) => [
300 + (r * Math.cos(theta)),
300 + (r * Math.sin(theta)),
]).join('L')}Z`;
class HexAPortal extends React.Component {
constructor(props) {
super(props);
this.state = {
sideCount: 6,
radius: 150,
};
}
componentDidMount() {
const ease = Linear.easeNone;
const tl = new TimelineMax({
repeat: -1,
onUpdate: () => {
TweenMax.to([
this.chaser1,
this.chaser2,
this.chaser3,
this.chaser4,
this.chaser5,
], 1, {
strokeDashoffset: '-=450',
});
},
});
tl.to(this.canvas, 2, {
rotation: -360,
transformOrigin: '50% 50%',
ease,
});
tl.timeScale(1);
}
update = (prop, value) => {
this.setState({ [prop]: Number(value) });
};
render() {
const { sideCount, radius } = this.state;
return (
<div className="bg-black vh-100 ph4 flex flex-column">
<svg
className="mw7 center mb4 db flex-auto"
ref={(canvas) => { this.canvas = canvas; }}
viewBox="0 0 600 600"
fill="none"
>
<defs>
<filter id="glow" y="-50%" height="200%">
<feGaussianBlur stdDeviation="12 1" result="coloredBlur" />
<feGaussianBlur stdDeviation="12 1" result="coloredBlur" />
<feGaussianBlur stdDeviation="12 1" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<path
id="hexPath"
d={pathDef(polygon(sideCount, radius))}
fill="none"
strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"
strokeMiterlimit="10" strokeDasharray="112"
/>
</defs>
<g filter="url(#glow)" >
<use
xlinkHref="#hexPath"
ref={(p) => { this.chaser1 = p; }}
stroke="#FFFFFF" strokeDashoffset="10100"
/>
<use
xlinkHref="#hexPath"
ref={(p) => { this.chaser2 = p; }}
stroke="#0000FF" strokeDashoffset="10060"
/>
<use
xlinkHref="#hexPath"
ref={(p) => { this.chaser3 = p; }}
stroke="#00FF00" strokeDashoffset="10040"
/>
<use
xlinkHref="#hexPath"
ref={(p) => { this.chaser4 = p; }}
stroke="#FF0000" strokeDashoffset="10020"
/>
<use
xlinkHref="#hexPath"
ref={(p) => { this.chaser5 = p; }}
stroke="#1d1d1d" strokeDashoffset="10000"
/>
</g>
</svg>
<div className="flex mb4 mw7 center">
<label htmlFor="sideCount" className="f5 ttu tracked b w5 tr dib mr3 white">
Number of Sides <span className="gray">({ sideCount })</span>
</label>
<input
name="sideCount" type="range" className="input-range-rv flex-auto white"
min="3" max="16" step="1"
value={sideCount}
onChange={e => this.update('sideCount', e.target.value)}
/>
</div>
<div className="flex mb5 mw7 center">
<label htmlFor="radius" className="f5 ttu tracked b w5 tr dib mr3 white">
Radius <span className="gray">({ radius })</span>
</label>
<input
name="radius" type="range" className="input-range-rv flex-auto white"
min="100" max="300" step="1"
value={radius}
onChange={e => this.update('radius', e.target.value)}
/>
</div>
</div>
);
}
}
ReactDOM.render(
<HexAPortal />,
document.getElementById('hex-a-portal')
);
View Compiled