Skip to content

Instantly share code, notes, and snippets.

@git2358
Created May 10, 2019 00:52
Show Gist options
  • Select an option

  • Save git2358/8474ff2a3025ec6167661ec0e9dfe858 to your computer and use it in GitHub Desktop.

Select an option

Save git2358/8474ff2a3025ec6167661ec0e9dfe858 to your computer and use it in GitHub Desktop.
React SVGatch
<div id="root"></div>

React SVGatch

Practice with React and SVG to create a rather enticing visual, for a simple clock displaying the current time.

A Pen by Gabriele Corti on CodePen.

License.

const { Component } = React;
// digits component, taking in the number of digits to show and mapping the digits around the clock
// for instance 4 digits --> 3, 6, 9, 12, positioned at 3, 6, 9, 12 o'click respectively
const Digits = ({ howMany, distance }) => {
// describe the digits to be displayed around the clock
const baseDigit = 12 / howMany;
const digits = [];
for (let i = baseDigit; i <= 12; i += baseDigit) {
digits.push(i);
}
// describe the text elements rotated according to the digit number (12 at the very top, 3 at the very right and so forth repeating the structure of an analog clock)
const DigitsText = digits.map(digit => {
const rotate = `rotate(${digit * 360 / 12}) translate(0, -${distance}) rotate(-${digit * 360 / 12})`;
return (
<text
key={digit}
x="0"
y="0"
fill="#ccc"
opacity="0.5"
fontWeight="400"
fontSize="0.85rem"
textAnchor="middle"
alignmentBaseline="middle"
transform={rotate}
>
{digit}
</text>
);
});
return (
<g>
{
DigitsText
}
</g>
);
}
// hours component, taking the current hours and drawing a hand rotated as per the number of hours
// 12 hours clock
const Hours = ({ hours, size }) => {
const hourTwelve = hours >= 12 ? hours -= 12 : hours;
const d = `M 0 0 v -${size}`;
const rotate = `rotate(${hourTwelve * 360 / 12})`;
return (
<g className="hand">
<path
stroke="#999"
strokeWidth="5px"
strokeLinecap="round"
fill="none"
d={d}
transform={rotate} />
</g>
);
};
// minutes component, taking the current minutes and drawing a hand rotated as per the number of minutes
const Minutes = ({ size, minutes }) => {
const d = `M 0 0 v -${size}`;
const rotate = `rotate(${minutes * 360 / 60})`;
return (
<g className="hand">
<path
// thinner and with a more evident color
stroke="#eee"
strokeWidth="3px"
strokeLinecap="round"
fill="none"
d={d}
transform={rotate} />
</g>
);
};
// seconds component, taking the current seconds, drawing 60 ticks for each second in the dial and highlighting the current second
// additionally detailing a triangle pointing toward the current second
const Seconds = ({ size, seconds, spread, turn }) => {
// seconds ticks
const SecondsPath = [];
for (let i = 1; i <= 60; i++) {
const rotate = `rotate(${i * 360 / 60})`;
const d = `M 0 -${spread} v -${size}`;
SecondsPath.push(
<path
key={i}
stroke="#eee"
strokeWidth="2px"
strokeLinecap="square"
fill="none"
className={(i === seconds + 1) ? 'current' : ''}
d={d}
transform={rotate} />
);
}
// triangle marker
// turn allowing to go past the 0-360 range
const markerPath = `M 0 -${spread - 5} l 5 7 h -10 Z`;
const markerRotation = `rotate(${(seconds + 1) * 360 / 60 + (360 * turn)})`;
return (
<g>
{/* ticks nested in a group to specifically target the path elements in CSS */}
<g className="seconds">
{
SecondsPath
}
</g>
<path
stroke="none"
fill="#eee"
d={markerPath}
transform={markerRotation} />
</g>
);
};
// main component rendered through index.js
class SVGatch extends Component {
constructor(props) {
super(props);
// in the state detail a date Object, later updated every second
// additionally define root variables for the size of the SVG and the margin allowing to nest SVG element inside, without cropping
// following d3's margin convention
// ! turn addded to keep track of the number of times the seconds marker goes around the clock, and in so doing avoiding the 'snap' occurring between 359 degrees and 0
// this way, instead of going back to rotate(0), the hands and marker go to rotate(360), then rotate(361) and so forth and so on
this.state = {
date: new Date(),
size: 225,
margin: 25,
turn: 0
};
}
// when the components are mounted set up an interval to update the date object every second
componentDidMount() {
this.interval = setInterval(() => {
const date = new Date();
// check if the number of seconds is 0, and in this instance increment the turn variable of the matching element
let turn = date.getSeconds() === 0 ? this.state.turn + 1 : this.state.turn;
// update the state
this.setState({
date,
turn
});
}, 1000);
}
render() {
// destructure the variables present in the state
const { date, size, margin, turn } = this.state;
/* define transform values to translate the SVG elements (per d3's margin convention)
and to center the elements */
const translate = `translate(${margin} ${margin})`;
const center = `translate(${size / 2} ${size / 2})`;
/* within the SVG Element, define the following elements/components
circle, for the outer most ring
Digits, for text elements distributed around the ring
Hours, for the hour's hand
Minutes, for the minute's hand
Seconds, for the ticks representing the seconds and a marker highlighting the current second
*/
return (
<svg width={size + (margin * 2)} height={size + (margin * 2)}>
<g transform={translate}>
<g transform={center}>
<circle
cx="0"
cy="0"
r={size / 2.3}
fill="none"
stroke="#ccc"
strokeWidth="2px" />
{/*
howMany: the number of digits to display around the clock
distance: where the text elements are pushed
*/}
<Digits
howMany={4}
distance={size / 2} />
{/*
hours: the current hours
size: the size of the hand
turn: to rotate the hand more than one time around the clock, and without transitioning issues
*/}
<Hours
hours={date.getHours()}
size={size / 4.5}
/>
{/*
minutes: the current minutes
size: the size of the hand
turn: to rotate the hand more than one time around the clock, and without transitioning issues
*/}
<Minutes
minutes={date.getMinutes()}
size={size / 3.5}
/>
{/*
seconds: the current seconds
size: the size of the hand
spread: the starting point of each tick
turn: to rotate the hand more than one time around the clock, and without transitioning issues
*/}
<Seconds
seconds={date.getSeconds()}
size={size / 30}
spread={size / 2.7}
turn={turn}
/>
<circle
cx="0"
cy="0"
r="5"
fill="#5941f3"
stroke="#fff"
strokeWidth="3px" />
</g>
</g>
</svg>
);
}
}
ReactDOM.render(<SVGatch />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/styled-components/3.4.10/styled-components.min.js"></script>
@import url("https://fonts.googleapis.com/css?family=Open+Sans");
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 100vh;
width: 100%;
background: #5941f3;
font-family: "Open Sans", sans-serif;
}
/* center the SVG in the page */
svg {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
/* transition for all path elements */
svg g path {
transition: all 1s linear;
}
svg g path.fix {
transition: none;
transform: rotate(0);
}
/* transition for the path elements representing the seconds ticks
as to 'maintain' so to speak an impression of the current tick, increase the duration of the transition */
svg g.seconds path {
opacity: 0.2;
transition: 10s linear all;
}
svg g.seconds path.current {
opacity: 1;
stroke-width: 3px;
transition: 1s linear all;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment