基于React与SVG的动态文本自动换行问题求助(使用mechanic.design框架)
Since SVG doesn’t have native support for automatic text wrapping, we can build a React-specific workaround to split dynamic text into lines that fit within your SVG’s bounds. Here’s a practical implementation tailored to your code:
Step 1: Build a Text Wrapping Utility Function
First, we’ll create a function that measures text width and splits it into lines that fit your desired maximum width. We’ll use a hidden SVG <text> element to get accurate dimension measurements (this matches your SVG’s font styling better than canvas):
const wrapText = (text, maxWidth, fontSize, fontFamily, fontWeight) => { const lines = []; if (!text) return lines; const words = text.split(' '); let currentLine = words[0]; // Create a hidden text element to measure width const tempText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); tempText.style.visibility = 'hidden'; tempText.style.fontSize = `${fontSize}px`; tempText.style.fontFamily = fontFamily; tempText.style.fontWeight = fontWeight; document.body.appendChild(tempText); for (let i = 1; i < words.length; i++) { const word = words[i]; tempText.textContent = `${currentLine} ${word}`; const lineWidth = tempText.getBBox().width; if (lineWidth <= maxWidth) { currentLine += ` ${word}`; } else { lines.push(currentLine); currentLine = word; } } lines.push(currentLine); document.body.removeChild(tempText); return lines; };
Step 2: Integrate the Wrapper into Your Component
Modify your handler component to use this function, rendering each wrapped line as a <tspan> inside your existing <text> elements. We’ll use useMemo to avoid recalculating lines unnecessarily:
import React, { useEffect, useMemo } from "react"; import ImgBanner from './assets/banner.svg'; import "./styles.css"; // Add the wrapText function here export const handler = ({ inputs, mechanic }) => { const { width, height, schriftfarbe, subtitel, titel, titel2, untertitel, href } = inputs; // Calculate max width for text (subtract 50px for left/right padding) const textMaxWidth = width - 50; // Wrap dynamic text lines const wrappedSubtitel = useMemo(() => wrapText(subtitel, textMaxWidth, 30, 'Garnett Regular', 'regular'), [subtitel, textMaxWidth] ); const wrappedTitel = useMemo(() => wrapText(titel, textMaxWidth, 72, 'Garnett Regular', 'regular'), [titel, textMaxWidth] ); const wrappedTitel2 = useMemo(() => wrapText(titel2, textMaxWidth, 72, 'Garnett Regular', 'regular'), [titel2, textMaxWidth] ); const wrappedUntertitel = useMemo(() => wrapText(untertitel, textMaxWidth, 30, 'Garnett Regular', 'regular'), [untertitel, textMaxWidth] ); useEffect(() => { mechanic.done(); }, []); return ( <svg width={width} height={height}> <rect width={width} height={height} /> <image href={ImgBanner} width={width} height={height} /> <image x={950} y={-10} width={200} height={200} href={href}></image> {/* Wrapped Subtitel */} <text x={25} y={75} fill={schriftfarbe} textAnchor="start" fontWeight="regular" fontFamily="Garnett Regular" fontSize={30}> {wrappedSubtitel.map((line, idx) => ( <tspan key={idx} x={25} dy={idx === 0 ? 0 : 35}>{line}</tspan> ))} </text> {/* Wrapped Titel */} <text x={25} y={height / 2} fill={schriftfarbe} textAnchor="start" fontWeight="regular" fontFamily="Garnett Regular" fontSize={72}> {wrappedTitel.map((line, idx) => ( <tspan key={idx} x={25} dy={idx === 0 ? 0 : 80}>{line}</tspan> ))} </text> {/* Wrapped Titel2 */} <text x={25} y={height / 2 + 90} fill={schriftfarbe} textAnchor="start" fontWeight="regular" fontFamily="Garnett Regular" fontSize={72}> {wrappedTitel2.map((line, idx) => ( <tspan key={idx} x={25} dy={idx === 0 ? 0 : 80}>{line}</tspan> ))} </text> {/* Wrapped Untertitel */} <text x={25} y={575} fill={schriftfarbe} textAnchor="start" fontWeight="regular" fontFamily="Garnett Regular" fontSize={30}> {wrappedUntertitel.map((line, idx) => ( <tspan key={idx} x={25} dy={idx === 0 ? 0 : 35}>{line}</tspan> ))} </text> </svg> ); };
Key Details to Adjust:
- Line Spacing: The
dyvalue on<tspan>controls vertical space between lines. Tweak35(for 30px text) or80(for 72px text) to match your font’s natural line height. - Padding: The
textMaxWidthsubtracts 50px from your SVG width to match your 25px left padding—adjust this if you need different spacing. - Long Words: If you have single words that exceed the max width, you can add extra logic to split them mid-word (e.g., insert hyphens) if needed.
This setup will automatically wrap your dynamic text within the SVG bounds, no manual line breaks required!
内容的提问来源于stack exchange,提问作者Kasper Salt

