Обрезать (с помощью многоточия) текст SVG до заданной ширины пикселя - PullRequest
0 голосов
/ 27 февраля 2019

У меня есть svg rect и svg text, созданные с помощью React.Текст может быть коротким или очень длинным, я хочу просто обрезать (с помощью многоточия) текст, если его ширина превышает пороговое значение.

Я знаю, что есть много вопросов по этому поводу (например, этот ) но я пока не смог решить проблему.

Это мой код:

interface Props extends InterfaceState {
  rectWidth: number
  spaceBetweenRects: number
}

export class Labels extends React.Component<Props> {
  render() {
    const { rectWidth, labelRectHeight, spaceBetweenRects } = this.props

    const names = ['Cher', 'Madonna', 'Band Names That Are Ridiculously Long', 'U2']
    const rectHeight = labelRectHeight
    const paddingLeftRight = 0.05 * rectWidth
    const plusWidth = rectHeight / 2

    return (
      <g>
        {names.map((name, i) => {
          const y = i * labelRectHeight + i * spaceBetweenRects
          const maxTextwidth = rectWidth - plusWidth - 3 * paddingLeftRight // this is the maximum length that text can have

          return (
            <g key={i}>
              <React.Fragment>
                <rect x={0} y={y} width={rectWidth} height={rectHeight} fill={'#E4E9EB'} />
              </React.Fragment>

              <text
                ref={this.textRef}
                x={rectWidth - paddingLeftRight}
                y={y + rectHeight / 2}
                textAnchor="end"
                alignmentBaseline="middle"
              >
                {name}
              </text>
            </g>
          )
        })}
      </g>
    )
  }
}

Это результат:

enter image description here

Как видите, Band Names That Are Ridiculously Long слишком длинный, поэтому я хочу что-то вроде Band Names That Are Ridiculously....

Я пытаюсь использовать React Ref:

export class Labels extends React.Component<Props> {
  textRef = React.createRef<SVGTextElement>()

  /**
   * Places textString in textObj and adds an ellipsis (...) if text can't fit in width.
   * TODO: type of textObject (RefObject<SVGTextElement> doesn't work)
   */
  placeTextWithEllipsis(textObj: any, textString: string, width: number) {
    console.log('textObj: ', textObj) // null :()
    textObj.textContent = textString
    if (textObj.getSubStringLength(0, textString.length) >= width) {
      for (let x = textString.length - 3; x > 0; x -= 3) {
        if (textObj.getSubStringLength(0, x) <= width) {
          textObj.textContent = textString.substring(0, x) + '...'
          return
        }
      }
      textObj.textContent = '...' // can't place at all
    }
  }

  render() {
    const { rectWidth, labelRectHeight, spaceBetweenRects } = this.props

    const names = ['Cher', 'Madonna', 'Band Names That Are Ridiculously Long', 'U2']
    const rectHeight = labelRectHeight
    const paddingLeftRight = 0.05 * rectWidth
    const plusWidth = rectHeight / 2

    return (
      <g>
        {names.map((name, i) => {
          const y = i * labelRectHeight + i * spaceBetweenRects
          const maxTextwidth = rectWidth - plusWidth - 3 * paddingLeftRight // this is the maximum length that text can have

          this.placeTextWithEllipsis(this.textRef, name, maxTextwidth)

          return (
            <g key={i}>
              <React.Fragment>
                <rect x={0} y={y} width={rectWidth} height={rectHeight} fill={'#E4E9EB'} />
              </React.Fragment>

              <text
                ref={this.textRef}
                x={rectWidth - paddingLeftRight}
                y={y + rectHeight / 2}
                textAnchor="end"
                alignmentBaseline="middle"
              >
                {name}
              </text>
            </g>
          )
        })}
      </g>
    )
  }
}

В этом случае textObj равно нулю ...

Последняя альтернатива, которую я попробовал, - это использование textPath , но в этом случаеЯ бы обрезал только строку, а не 3 последние точки, поэтому мне не очень нравится это решение.

Я был бы рад, если бы кто-нибудь помог мне, потому что я не знаю, что попробовать.Большое спасибо

Ответы [ 2 ]

0 голосов
/ 08 мая 2019

У меня такая же проблема.Я попробовал способ, предложенный лунатизмом, но он не работал для меня.Я использовал это немного по-другому с хуком api, useRef и useLayoutEffect.Поэтому я предложил другое решение, использующее foreignObject в svg.Таким образом, мы можем использовать стандартное решение html и css.

  <foreignObject x={0} y={0} width={maxTextwidth} height={20}>
    <div style={{textOverflow: 'ellipsis', with: maxTextwidth, overflow: 'hidden', whiteSpace: 'nowrap'}}>
      Long text to use ellipsis on
    </div>
  </foreignObject>
0 голосов
/ 27 февраля 2019

textObj является нулевым в методе рендеринга.Вы должны это использовать в componentDidMount().Затем создайте массив ref, в противном случае ссылка будет переопределена каждым элементом цикла.

export class Labels extends React.Component<Props> {
  textRefs = new Array(React.createRef<SVGTextElement>())

  componentDidMount() {
    const { maxTextwidth } = this.props
    this.textRefs.forEach((ref, i) => {
      placeTextWithEllipsis(
        this.textRefs[i].current,
        this.textRefs[i].current.innerHTML,
        maxTextwidth
      )
    })
  }

  render() {
    return (
      // ...
      <text
        ref={(this.textRefs[i] = React.createRef<SVGTextElement>())}
        x={rectWidth - paddingLeftRight}
        y={y + rectHeight / 2}
        textAnchor="end"
        alignmentBaseline="middle"
      >
        {...}
      </text>
      // ...
    )
  }
}
...