JSFiddle - React, Tailwind, and code Playground
by RAX7
HTML
<div class="container">
<svg id="chart" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1920 997">
<defs>
<pattern id="dot-pattern" x="-43" y="-19" width="65" height="50" patternUnits="userSpaceOnUse" viewBox="0 0 65 50">
<rect width="65" height="50" fill="none"/>
<circle cx="65" cy="50" r="2" fill="#fff" opacity="0.2"/>
<circle cy="50" r="2" fill="#fff" opacity="0.2"/>
<circle cx="65" r="2" fill="#fff" opacity="0.2"/>
<circle r="2" fill="#fff" opacity="0.2"/>
</pattern>
<symbol id="marker-start-end" viewBox="0 0 47 47">
<circle cx="23.5" cy="23.5" r="14" fill="#ff9b50"/>
<circle cx="23.5" cy="23.5" r="23" fill="none" stroke="#ff9b50"/>
</symbol>
</defs>
<rect id="bg-solid-color" width="1920" height="997" fill="#1e1e22"/>
<rect id="bg-dot-pattern" width="1920" height="997" fill="url(#dot-pattern)"/>
<path id="chart-line" d="M121,838s8,13,36,13c50.5,0,95.5-44,150-44,27,0,70.5,33.5,112.5,33.5s79-40,111-40,48,36,83,36c79,0,117-44,159-44s70,26,117,26c94,0,160-125,224-125,42,0,109-3,165-45,51.5-38.6,102-126,167-175,66.6-50.2,209-71,271-212" fill="none" stroke="#fff" stroke-width="4"/>
<path id="chart-range" d="M121,838s8,13,36,13c50.5,0,95.5-44,150-44,27,0,70.5,33.5,112.5,33.5s79-40,111-40,48,36,83,36c79,0,117-44,159-44s70,26,117,26c94,0,160-125,224-125,42,0,109-3,165-45,51.5-38.6,102-126,167-175,66.6-50.2,209-71,271-212" fill="none" stroke="#ff9b50" stroke-width="8" stroke-dashoffset="1845.003662109375" stroke-dasharray="1845.003662109375"/>
<use width="47" height="47" transform="translate(98 816)" xlink:href="#marker-start-end"/>
<use width="47" height="47" transform="translate(1693 238)" xlink:href="#marker-start-end"/>
<path id="slider" d="M-1,58S-7,70-26,70l10-9V19H-29L-1,0Zm2,0S7,70,26,70L16,61V19H29L1,0Z" fill="#ff9b50" transform="translate(121 863)"/>
</svg>
</div>
CSS
body {
margin: 0;
padding: 0;
}
#slider {
cursor: pointer;
}
JavaScript
const MAX_PATH_SEGMENTS_WIDTH = 2;
const sliderBottomOffset = 25;
const svg = document.querySelector('#chart');
const chartLine = document.querySelector('#chart-line');
const chartRange = document.querySelector('#chart-range');
const slider = document.querySelector('#slider');
const chartPoints = splitIntoSegments(chartLine, MAX_PATH_SEGMENTS_WIDTH);
const chartTotalLength = chartLine.getTotalLength();
chartRange.setAttributeNS(null, 'stroke-dasharray', chartTotalLength);
chartRange.setAttributeNS(null, 'stroke-dashoffset', -chartTotalLength);
slider.addEventListener('mousedown', function() {
svg.addEventListener('mousemove', onMouseMove);
});
document.addEventListener('mouseup', function() {
svg.removeEventListener('mousemove', onMouseMove);
});
function onMouseMove(e) {
const {x: mouseX, y: mouseY} = svgPoint(svg, e.clientX, e.clientY);
const chartSegment = calcIntersectionX(mouseX, chartPoints);
let x, y, l;
if (chartSegment[0] === chartSegment[1]) {
x = chartSegment[0].x;
y = chartSegment[0].y;
l = chartSegment[0].l;
}
else {
const dx = Math.abs(chartSegment[1].x - chartSegment[0].x);
const xRatioOffset = (chartSegment[1].x - mouseX) / dx;
const dy = Math.abs(chartSegment[1].y - chartSegment[0].y);
const dl = Math.abs(chartSegment[1].l - chartSegment[0].l);
x = mouseX;
y = chartSegment[0].y + dy * xRatioOffset;
l = chartSegment[0].l + dl * xRatioOffset;
}
slider.setAttributeNS(null, 'transform', `translate(${x} ${y + sliderBottomOffset})`);
chartRange.setAttributeNS(null, 'stroke-dashoffset', `${chartTotalLength - l}`);
}
// translate page to SVG co-ordinate
function svgPoint(svg, x, y) {
const pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt.matrixTransform(svg.getScreenCTM().inverse());
}
function splitIntoSegments(path, maxWidth = 2) {
const pathLength = path.getTotalLength();
const segmentsCount = Math.ceil(pathLength / maxWidth);
const points = []
for...