Skip to content

Instantly share code, notes, and snippets.

@siliconjungle
Created May 21, 2024 15:55
Show Gist options
  • Save siliconjungle/c8695fa319114226577d4e22c05acdc6 to your computer and use it in GitHub Desktop.
Save siliconjungle/c8695fa319114226577d4e22c05acdc6 to your computer and use it in GitHub Desktop.
goopy text
path {
fill: none;
stroke: rgba(52, 53, 60, 1);
stroke-width: 4;
stroke-linecap: round;
}
svg > text {
font-family: 'Geist', sans-serif !important;
fill: rgb(201, 213, 229);
pointer-events: none;
user-select: none;
font-size: 30px;
}
import { useState, useRef, useMemo, useEffect } from 'react';
import Stack from 'lib/stack'
const DraggablePointsSVG = ({ text, points, setPoints }) => {
const [selectedPoint, setSelectedPoint] = useState(null);
const svgRef = useRef(null);
const [isSelected, setIsSelected] = useState(false);
const pathRef = useRef(null);
const textPathRef = useRef(null);
const boxRef = useRef(null);
const handleMouseMove = (e) => {
if (selectedPoint !== null) {
const svg = svgRef.current;
const rect = svg.getBoundingClientRect();
const newPoints = points.slice();
newPoints[selectedPoint] = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
setPoints(newPoints);
}
};
const handleMouseUp = () => setSelectedPoint(null);
const handleMouseLeave = () => setSelectedPoint(null);
useEffect(() => {
if (pathRef.current === null) return;
if (textPathRef.current === null) return;
if (boxRef.current === null) return;
let d = `M ${points[0].x},${points[0].y}`;
if (points.length > 2) {
for (let i = 0; i < points.length - 1; i++) {
const p0 = i > 0 ? points[i - 1] : points[0];
const p1 = points[i];
const p2 = points[i + 1];
const p3 = i + 2 < points.length ? points[i + 2] : p2;
const tension = 0.2;
const cp1x = p1.x + (p2.x - p0.x) * tension;
const cp1y = p1.y + (p2.y - p0.y) * tension;
const cp2x = p2.x - (p3.x - p1.x) * tension;
const cp2y = p2.y - (p3.y - p1.y) * tension;
d += ` C ${cp1x},${cp1y} ${cp2x},${cp2y} ${p2.x},${p2.y}`;
}
} else if (points.length === 2) {
d += ` L ${points[1].x},${points[1].y}`;
}
pathRef.current.setAttribute('d', d);
textPathRef.current.setAttribute('textLength', pathRef.current.getTotalLength().toString());
// get the bounds of the textPathRef and set the boxRef to those bounds
const textPathBounds = textPathRef.current.getBBox();
boxRef.current.setAttribute('x', textPathBounds.x);
boxRef.current.setAttribute('y', textPathBounds.y);
boxRef.current.setAttribute('width', textPathBounds.width);
boxRef.current.setAttribute('height', textPathBounds.height);
}, [points, pathRef.current, textPathRef.current, boxRef.current]);
return (
<div
style={{
position: 'relative',
width: '100%',
height: '100%',
overflow: 'hidden',
}}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseLeave}
onMouseDown={() => {
if (selectedPoint !== null) return;
setIsSelected(false);
}}
>
<svg
ref={svgRef}
width="800"
height="600"
xmlns="http://www.w3.org/2000/svg"
>
<rect
fill="transparent"
// stroke="black"
ref={boxRef}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
setIsSelected(true);
}}
/>
<path
id="dynamicPath"
ref={pathRef}
style={{
...(isSelected ? {} : { stroke: 'transparent' })
}}
/>
<text>
<textPath
ref={textPathRef}
href="#dynamicPath"
lengthAdjust="spacingAndGlyphs"
>
{text}
</textPath>
</text>
</svg>
{isSelected && points.map((point, index) => (
<div
key={index}
style={{
position: 'absolute',
left: point.x - 10,
top: point.y - 10,
width: 20,
height: 20,
borderRadius: '50%',
cursor: 'pointer',
}}
onMouseDown={(e) => {
e.stopPropagation();
e.preventDefault();
setSelectedPoint(index)
}}
className="bento-stack frosted-card"
/>
))}
</div>
);
};
function Text() {
const [points, setPoints] = useState([
{ x: 100, y: 100 },
{ x: 200, y: 200 },
{ x: 300, y: 100 },
{ x: 400, y: 200 },
{ x: 500, y: 100 }
]);
return (
<Stack
direction="row"
gap="0.5em"
style={{
padding: '1em',
justifyContent: 'center',
}}
>
<DraggablePointsSVG
text="Navigating space & time..."
points={points}
setPoints={setPoints}
/>
</Stack>
);
}
export default Text
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment