The following article teaches how you can create a beautiful graph slider with only SVG.
https://rauno.me/craft/graph-slider/
The graph is an svg
element. And the rounded indicator itself has offset-path defined with the same path definition that renders the graph stroke. Basically, this property enables moving an element along a given path. For brevity, some details are omitted but here is the general idea:
<div>
<div
data-dot-indicator
style={{
position: 'absolute',
offsetPath: `path("${pathDefinition}")`
}}
/>
<svg>
<path
d={pathDefinition}
stroke="#C7C7C7"
strokeWidth="2.2"
strokeLinecap="round"
/>
</svg>
</div>
Now to move the indicator, on mouse move the mouse distance is mapped to offset-distance as a percentage:
<div
data-dot-indicator
style={{
position: 'absolute',
offsetPath: `path("${offsetPath}")`,
offsetDistance: ((e.clientX - parentLeft) / parentWidth) * 100 + '%',
}}
/>
We could also use offset-path
for the vertical indicator but I think that the label moving makes it tricky to read the value:
´
So we want to make sure the label position stays fixed, and translate the line with transform
. However, we can’t just map the mouse movement one-to-one since the dot moves along a non-linear path. It would be out of sync that way, like so:
Instead, we can calculate the distance between the start and its current position, and center it relative to the indicator dot:
const x = dotX - parentX + dotWidth / 2 - lineWidth / 2
return (
<div
data-vertical-indicator
style={{
position: 'absolute',
transform: `translateX(${x}px)`,
}}
/>
);
Lastly, in the SVG there are two paths on top of each other: a grayscaled and a colored one. To partially highlight the graph, clip-path
is used to reveal the colored elements based on the same variable from above:
const clipPath = `inset(0 ${parentWidth - x - lineWidth * 2}px 0 0)`;
return (
<svg>
<path d={gradientDefinition} fill="url(#grayscale)" />
<path d={gradientDefinition} fill="url(#color)" style={{ clipPath }} />
<g strokeWidth="2.2" strokeLinecap="round">
<path
d={strokeDefinition}
stroke="var(--colors-gray8)"
style={{ clipPath }}
/>
<path
d={strokeDefinition}
stroke="var(--colors-blue9)"
/>
</g>
</svg>
);
Acknowledgements
Thanks to Family’s iOS app for the idea, Siddhartha for the graph, and Paco and Jonnie for their help and feedback.