How to Create Smooth Drag‑Slider Effects with SVG Bezier Curves
This article explains how to use SVG path commands and JavaScript to draw and animate Bézier curves for a fluid drag‑slider UI, covering background, curve theory, implementation steps, stroke‑dash techniques, offset‑path alternatives, and SMIL animation for interactive effects.
Technical Sharing
This sharing covers the following aspects:
Background
Explanation of Bézier curves
Implementation and exploration process
Background
In a recent X‑business evaluation report page, a requirement emerged for users to drag a slider left or right to view information for each level.
Previously, the UI switched images, causing noticeable lag, especially because the level intervals are large.
To achieve a smoother effect, the solution uses SVG to draw Bézier curves, dynamically switching between solid and dashed lines and moving a hollow ball along the curve.
SVG path Tag
<svg>
<path
stroke="red"
fill="none"
d="M 4,130 C 12.85,129.1 45.3,126.7 63,124 C 80.7,121.3 104.3,116.5 122,112 C 139.7,107.5 163.3,100.3 181,94 C 198.7,87.7 222.3,78.1 240,70 C 257.7,61.9 281.3,49.9 299,40 C 316.7,30.1 349.15,9.4 358,4"></path>
</svg>The following commands are used (uppercase letters denote absolute coordinates):
M – moveto (set start point)
L – lineto (draw a line)
H – horizontal lineto
V – vertical lineto
C – curveto (cubic Bézier)
S – smooth curveto
Q – quadratic Bézier
T – smooth quadratic Bézier
A – elliptical arc
Z – closepath
Using lowercase letters would create relative paths.
Bézier Curves
A Bézier curve consists of line segments and draggable control points that determine the curve’s direction. By adjusting four points (start, end, and two control points), the curve’s shape can be edited; the lines from the end points to the control points form the “control lines”.
First‑order Bézier Curve
Formula:
B(t) = P1 + (P2 − P1)·t = P1·(1−t) + P2·t, t∈[0,1]Second‑order Bézier Curve
With three points P1, P2, P3 (P2 is the control point):
M = P1·(1‑t) + P2·t
N = P2·(1‑t) + P3·t
B(t) = M·(1‑t) + N·t
Third‑order Bézier Curve
Formula (using points P0‑P3):
const calculateCirclePoint = (t, PointArray) => {
const p0 = PointArray[0];
const p1 = PointArray[1];
const p2 = PointArray[2];
const p3 = PointArray[3];
const temp = 1 - t;
const x = p0.x*temp*temp*temp + 3*p1.x*t*temp*temp + 3*p2.x*t*t*temp + p3.x*t*t*t;
const y = p0.y*temp*temp*temp + 3*p1.y*t*temp*temp + 3*p2.y*t*t*temp + p3.y*t*t*t;
return {x, y};
};Determining Control Points in a Cubic Bézier
Direction: Ensure the first derivative (tangent) is continuous; the control‑point line’s slope matches the tangent.
Length: Controls curve “tightness”. A common heuristic is length = AC * 0.15 for smoothness.
For the start point A and end point D, the control points can be approximated by extending the line AB (or DC) with the calculated length.
Implementation Challenges
Switching between solid and dashed lines while dragging.
Moving a hollow ball along the curve in sync with the drag.
Animating the ball and curve after release.
SVG Basics – stroke-dasharray and stroke-dashoffset
stroke-dasharraycreates dashed lines, e.g., '10,5' means 10 px dash, 5 px gap, repeated. stroke-dashoffset shifts the dash pattern along the path; positive values move the pattern forward, negative values move it backward.
Example: codepen example
CSS offset-path and offset-distance
offset-pathdefines a custom motion path. offset-distance specifies how far along that path the element moves (0‑100%).
Examples: codepen example
Compatibility
Implementation Process
Step 1 – Draw Bézier Curve
The slope of line BC matches the slope of line x1‑x2.
Calculate Circle Center and Handle Coordinates
// Dynamic circle center coordinates
const PointArray = [
[4,130],[63,124],[122,112],[181,94],[240,70],[299,40],[358,4]
];
// Compute angle and length between two points
const line = (pointA, pointB) => {
const lengthX = pointB[0] - pointA[0];
const lengthY = pointB[1] - pointA[1];
return {
length: Math.sqrt(Math.pow(lengthX,2) + Math.pow(lengthY,2)),
angle: Math.atan2(lengthY, lengthX)
};
};
// Get control point for a segment
const controlPoint = (current, previous, next, reverse) => {
const p = previous || current;
const n = next || current;
const l = line(p, n);
const angle = l.angle + (reverse ? Math.PI : 0);
const length = l.length * BezierCurveAndCirclePoint.smoothing;
const x = current[0] + Math.cos(angle) * length;
const y = current[1] + Math.sin(angle) * length;
return [x, y];
};
const getBezierCurvePointArray = () => {
const array = [];
pointArray.forEach((item, i) => {
if (i === 0) return;
const cps = controlPoint(pointArray[i-1], pointArray[i-2], item, false);
const cpe = controlPoint(item, pointArray[i-1], pointArray[i+1], true);
array.push([
{x: pointArray[i-1][0], y: pointArray[i-1][1]},
{x: cps[0], y: cps[1]},
{x: cpe[0], y: cpe[1]},
{x: item[0], y: item[1]}
]);
});
return array;
};Draw Three Cubic Bézier Curves
Observation: Switching the green solid line to a green dashed line is equivalent to moving the solid line left via stroke-dashoffset. The gray solid line turning green dashed moves right.
Reference implementation: codepen example
Step 2 – Compute Curve Length
Determine how far stroke-dashoffset should move when the user drags 2 px.
Initial stroke-dasharray value is set based on the sampled length.
Calculate Cubic Bézier Length
const cubicBezierLength = (PointArray, sampleCount) => {
const ptCount = sampleCount || 40;
let totDist = 0;
let lastX = PointArray[0].x;
let lastY = PointArray[0].y;
for (let i = 1; i < ptCount; i++) {
const pt = calculateCirclePoint(i / ptCount, PointArray);
const dx = pt.x - lastX;
const dy = pt.y - lastY;
totDist += Math.sqrt(dx * dx + dy * dy);
lastX = pt.x;
lastY = pt.y;
}
const dx = PointArray[3].x - lastX;
const dy = PointArray[3].y - lastY;
totDist += Math.sqrt(dx * dx + dy * dy);
return Math.floor(totDist);
};Step 3 – Move Hollow Ball with Drag
Because offset-path is not supported on iOS, the ball’s coordinates are calculated in real time using the Bézier formula.
Step 4 – Animate Ball After Release
SVG SMIL Animation
<animateMotion/> <circle
opacity={circleAnimationPointOpacity ? '1' : '0'}
r="3"
fill="#ffffff"
strokeWidth="2"
stroke="#43E077">
<animateMotion
ref={circleAnimationRef}
path={cirlceAnimationPath}
dur="200ms"
keySplines="0.25 0.1 0.25 1"
fill="freeze"
begin="indefinite"
repeatCount="1"/>
</circle>Reference: codepen example
References
https://codepen.io/Josh_byte/pen/MWQVWKK
https://codepen.io/Josh_byte/pen/PoQzjON
https://zhuanlan.zhihu.com/p/31242043
https://codepen.io/Josh_byte/pen/bGLvKYM
https://codepen.io/Josh_byte/pen/oNEYpjO
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
