Reputation: 85
I have been trying to make this shape in svg. Problem is, I want to manipulate it with the blue handles. I have already made a simple arrow and am able to change its shape with quadratic bezier curves. But I am unable to figure out how to do it for this kind of shape. Is there some way to transform a line into this squiggly form?
Upvotes: 2
Views: 629
Reputation: 169032
You can use the getPointAtLength
and getTotalLength
APIs to "ride" along any arbitrary SVG geometry, and generate your sine wave.
Here's an example in plain TypeScript (find an interactive React CodeSandbox with a couple extra bells and whistles here).
function computeWave(
path: SVGPathElement,
freq: number,
maxAmp: number,
phase: number,
res: number
) {
// Get the points of the geometry with the given resolution
const length = path.getTotalLength();
const points = [];
if (res < 0.1) res = 0.1; // prevent infinite loop
for (let i = 0; i <= length + res; i += res) {
const { x, y } = path.getPointAtLength(i);
points.push([x, y]);
}
// For each of those points, generate a new point...
const sinePoints = [];
for (let i = 0; i < points.length - 1; i++) {
// Numerical computation of the angle between this and the next point
const [x0, y0] = points[i];
const [x1, y1] = points[i + 1];
const ang = Math.atan2(y1 - y0, x1 - x0);
// Turn that 90 degrees for the normal angle (pointing "left" as far
// as the geometry is considered):
const normalAngle = ang - Math.PI / 2;
// Compute the sine-wave phase at this point.
const pointPhase = ((i / (points.length - 1)) * freq - phase) * Math.PI * 2;
// Compute the sine-wave amplitude at this point.
const amp = Math.sin(pointPhase) * maxAmp;
// Apply that to the current point.
const x = x0 + Math.cos(normalAngle) * amp;
const y = y0 + Math.sin(normalAngle) * amp;
sinePoints.push([x, y]);
}
// Terminate the sine points where the shape ends.
sinePoints.push(points[points.length - 1]);
// Compute SVG polyline string.
return sinePoints
.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x},${y}`)
.join(" ");
}
which generates the blue line following the orange one (which is described as M100,100 C150,100,150,250,200,200
):
You can of course adapt this to e.g. "pinch" the wave at the ends, to avoid any abrupt ends with an arbitrary phase, etc.
Upvotes: 5
Reputation: 85
There is are no such transformations in SVG. So you have to find equally spaced points on the bezier curve and offset them according to the sinusuidal equation. This is a great video to explaining bezier curves and using a look up table to find equally spaced points on the arc: https://www.youtube.com/watch?v=aVwxzDHniEw
To understand how to offset the points, you need a bit co-ordinate geometry. I have created a Desmos graph to help you out: https://www.desmos.com/calculator/4lbhfcro8t
Notice that the sine curve in the above graph is not uniform. That is because the points used for offsetting are equally spaced 't' values. You have to use equally spaced arc lengths as demonstrated in the video.
Upvotes: 0