-
-
Save andupotorac/f7f7d5779a9628b92ec3a7a96877ffd4 to your computer and use it in GitHub Desktop.
goopy text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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