如何在React中测量SVG元素?

时间:2017-05-09 21:38:03

标签: reactjs svg

要在我的应用中渲染某些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的最佳方式是什么。

或者,有更好的方法吗?

1 个答案:

答案 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>
      );
    });

useEffect来检测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``;