要在我的应用中渲染某些SVG元素,我需要先测量一些其他SVG元素。
例如,假设一个<text>
元素随机放置在正方形(0,0) - (100,100)中,并且可以有各种字体大小,字体系列等。
如果文本位于(10,20),宽度为30,高度为40,我想将包含<svg>
宽度设置为40(= 10 + 30)并且高度为60(= 20 + 40)。
重点是:<text>
需要在呈现<svg>
之前进行衡量。
为了帮助进行<text>
测量,我创建了以下组件:
class MeasureSvgElements extends React.Component {
storeSvgReference = svg => {
if (svg !== null) {
this.svg = svg;
}
};
measure() {
const childElements = Array.from(this.svg.children);
const dimensions = childElements
.map(element => element.getBoundingClientRect())
.map(({ width, height }) => ({ width, height }));
this.props.onChange(dimensions);
}
componentDidMount() {
this.measure();
}
componentDidUpdate() {
this.measure();
}
render() {
return (
<svg width="0" height="0" style={{ display: 'block' }} ref={this.storeSvgReference}>
{this.props.children}
</svg>
);
}
}
可用于一次测量多个元素:
<MeasureSvgElements onChange={onChange}>
{['Hello', 'Stack', 'Overflow'].map(str => <text>{str}</text>)}
</MeasureSvgElements>
维度就绪后,将调用 onChange
。
现在,我不确定使用<MeasureSvgElements>
使用<svg>
提供的维度来呈现包含onChange
的最佳方式是什么。
或者,有更好的方法吗?
答案 0 :(得分:0)
一种解决方案可能是在渲染之前模拟每个text
元素,收集高度和宽度(除了位置),然后遍历各个尺寸以确定应将哪些尺寸应用于父级
工作代码沙盒:https://codesandbox.io/s/stack-43880276-dynamic-svg-textbox-id3rt
import React, { useState, useEffect } from "react";
import styled from "styled-components";
const yourTextData = [
{
id: 0,
posX: 0,
posY: 0,
str: "~~Hello~~",
fontFamily: "URW Chancery L, cursive",
fontSize: "30"
},
{
id: 1,
posX: 20,
posY: 20,
str: "~~Stack~~",
fontFamily: "URW Chancery L, cursive",
fontSize: "30"
},
{
id: 2,
posX: 40,
posY: 40,
str: "~~Overflow~~",
fontFamily: "URW Chancery L, cursive",
fontSize: "30"
},
{
id: 3,
posX: 0,
posY: 80,
str: "This SVG with text sets its own",
fontFamily: "Arial, sans-serif",
fontSize: "30"
},
{
id: 4,
posX: 40,
posY: 120,
str: "d i m e n s i o n s",
fontFamily: "FreeMono, monospace",
fontSize: "30"
}
];
const DynamicSVGText = (props) => {
const [svgWidth, setSvgWidth] = useState(0);
const [svgHeight, setSvgHeight] = useState(0);
const [textDims, setTextDims] = useState([]); // array of objects, defining dims/pos for each texteach text
在创建每个text
元素时将调用此方法,并将其保存到textDims
挂钩中。
const measure = (data) => {
// create a mock element
let newText = document.createElement("text");
newText.setAttribute("id", data.id);
document.body.appendChild(newText);
// append text data
let theTextEle = document.getElementById(`${data.id}`);
theTextEle.innerHTML += data.str;
// append text font / size, bc might as well be fancy
theTextEle.style.fontFamily = data.fontFamily;
theTextEle.style.fontSize = `${data.fontSize}px`;
// measure element
let width = theTextEle.getBoundingClientRect().width;
let height = theTextEle.getBoundingClientRect().height;
// delete element
theTextEle.parentNode.removeChild(theTextEle);
// set data
let dimData = [width, height];
//console.log(dimData);
// return dimension data
return dimData;
};
text
元素的函数: const SvgText = ({ text }) =>
text.map((data, i) => {
let dimensions = measure(data);
let updatedTextDims = textDims;
updatedTextDims[data.id] = {
x: data.posX,
y: data.posY,
w: dimensions[0],
h: dimensions[1]
};
return (
<StyledText
fontFamily={data.fontFamily}
fontSize={data.fontSize}
key={data.id}
x={data.posX.toString()}
y={(data.posY + dimensions[1]).toString()}
>
{data.str}
</StyledText>
);
});
textDims
挂钩何时更改 useEffect(() => {
let tw = 0; // longest width
let th = 0; // tallest height
// loop through text elements and their dimensions,
// set total width/height to the greatest dimensions across objects
for (let d = 0; d < textDims.length; d++) {
let thisWidth = textDims[d].x + textDims[d].w;
let thisHeight = textDims[d].y + textDims[d].h;
if (thisWidth > tw) {
tw = thisWidth;
}
if (thisHeight > th) {
th = thisHeight;
}
}
setSvgWidth(tw);
setSvgHeight(th);
}, [textDims]);
return (
<svg
width={svgWidth.toString()}
height={svgHeight.toString()}
style={{ display: "block" }}
>
<SvgText text={props.text} />
</svg>
);
};
export default function App() {
return (
<Container>
<DynamicSVGText text={yourTextData} />
</Container>
);
}
const Container = styled.div`
display: flex;
flex-direction: row;
justify-content: center;
`;
const StyledText = styled.text``;