我正在编写一个使用 React 生成 SVG 的可视化应用程序。我需要的部分之一是标签 - 即文本,由封闭框包围,带有可变文本,可能会旋转和设置样式。
所以我有一个组件NodeLabel
,目前具有固定尺寸:
render() {
return <g>
<rect className="label" x={this.props.x} y={this.props.y-10} width={20} height={40}></rect>
<text className="labelText" x={this.props.x} y={this.props.y}>{this.props.children}</text>
</g>
}
我在这里找到了一些关于在 DOM 中执行此操作的信息:SVG 文本周围的矩形边框 https://stackoverflow.com/questions/17218108/rectangle-border-around-svg-text
但我不太明白如何将其转换为 React 组件 - 在 render() 方法内,没有 DOM 元素可供查看。我可以只使用document.createElement()
相反并期望 SVG 元素的尺寸能够正常运行(并遵循 CSS)?另外,有没有一种方法可以避免本质上有两份创建代码副本,一份在 JSX 中,另一份就在这之前来计算尺寸? (例如,评估这个临时离屏副本的 JSX 到 DOM 元素的片段)
更新:2018 年 1 月,我又回来了:-) 实际应用程序是一个开源网络图表工具,目前使用 GD 和 PHP,但我希望能转向 JS、React 和 SVG。
这里的带宽标签是我试图重现的,尽管节点标签在当前的非 SVG 版本中使用相同的功能。
这是我的新的最小示例:
// MyLabel should be centred at x,y, rotated by angle,
// and have a bounding box around it, 2px from the text.
class MyLabel extends React.Component {
render() {
const label = <text x={this.props.x} y={this.props.y} textAnchor="middle" alignmentBaseline="central">{this.props.children}</text>;
// label isn't a DOM element, so you can't call label.getBoundingClientRect() or getBBox()
// (Magic happens here to find bbox of label..)
// make up a static one for now
let bb = {x: this.props.x-20, y: this.props.y-6, width: 40, height: 12};
// add margin
const margin = 2;
bb.width += margin * 2;
bb.height += margin * 2;
bb.x -= margin;
bb.y -= margin;
// rect uses bbox to decide its size and position
const outline = <rect x={bb.x} y={bb.y} width={bb.width} height={bb.height} className="labeloutline"></rect>;
const rot = `rotate(${this.props.angle} ${this.props.x} ${this.props.y})`;
// build the final label (plus an x,y spot for now)
return <g transform={rot}>{outline}{label}<circle cx={this.props.x} cy={this.props.y} r="2" fill="red" /></g>;
}
}
class Application extends React.Component {
render() {
return <svg width={300} height={300}>
<MyLabel x={100} y={100} angle={0}>Dalmation</MyLabel>
<MyLabel x={200} y={100} angle={45}>Cocker Spaniel</MyLabel>
<MyLabel x={100} y={200} angle={145}>Pug</MyLabel>
<MyLabel x={200} y={200} angle={315}>Pomeranian</MyLabel>
</svg>;
}
}
/*
* Render the above component into the div#app
*/
ReactDOM.render(<Application />, document.getElementById('app'));
body { background: gray; }
svg {background: lightgray;}
.labeloutline { fill: white; stroke: black;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
您可以预先计算/测量字体几何形状,并根据输入字符串获得文本尺寸的合理估计(这是最简单的解决方案,但如果字体发生变化,显然会破坏),或者执行两阶段渲染:
也就是说,您通过 ref 获取 dom 元素,在挂载上获取框,最后通过更新状态重新渲染,如下所示:
class MyLabel extends React.Component {
constructor(props){
super(props);
this.state = {text_extents:null};
}
componentDidMount() {
const box = this.text.getBBox();
this.setState({text_extents:[box.width,box.height]});
}
render() {
const margin = 2;
const extents = this.state.text_extents;
const label = <text ref={(t) => { this.text = t; }} textAnchor="middle" dy={extents?(extents[1]/4):0} >{this.props.children}</text>;
const outline = extents ?
<rect x={-extents[0]/2-margin} y={-extents[1]/2-margin} width={extents[0]+2*margin} height={extents[1]+2*margin} className="labeloutline"></rect>
: null;
return <g transform={`translate(${this.props.x},${this.props.y}) rotate(${this.props.angle})`}>{outline}{label}</g>;
}
}
请注意,根据最新的反应文档,这不应导致任何用户可见的闪烁:
组件DidMount():在此方法中调用 setState() 将触发额外的渲染,但它会在浏览器更新屏幕之前发生。这保证了即使在这种情况下 render() 被调用两次,用户也不会看到中间状态。请谨慎使用此模式,因为它通常会导致性能问题。然而,对于模态和工具提示等情况,当您需要在渲染依赖于其大小或位置的内容之前测量 DOM 节点时,它可能是必要的。
最后,请注意,如果标签字符串发生更改(通过 props 或其他方式),您将需要相应地更新范围(通过componentDidUpdate()
).
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)