caasih.net

React Konva

React Konva 將 Canvas 繪圖函式庫 Konva.js 包裝成 React components 。 Konva.js 則將 Canvas 操作包裝好,提供容器(例如 Group )將形狀們包裝起來,方便位移、旋轉。

乍看很美好,但 Konva.js 不提供像 HTML 或 SVG 那樣的排版功能。

Rect 和 Text 彼此無關

可以給 Konva.js Text 特定寬、高,來限制繪製區域。若希望在繪製完後根據文字寬高畫其他元件,則需要多包裝一下。

下面提到的元件都以 Flow 標註型別,有著類似結構:

/* @flow */

import React, { PureComponent } from 'react'

type Props = {
  id?: string,
  className: string
}

class Comp extends PureComponent<Props> {
  static defaultProps = {
    className: ''
  }

  render() {
    const { id, className } = this.props

    return (
      <div id={id} className={className}>
        This is a component.
      </div>
    )
  }
}

export default Comp

為了取得文字寬高,得用上 ref 來存取 React 包裝起來的繪製結果。平常取得的 ref 是 DOM node ,在 React Konva 下,我們拿到的是 Konva.Text 。

return (
  <KonvaText
    {...props}
    ref={node => this.textNode = node}
  />
)

接著在 componentDidMount 和 componentDidUpdate 時,就可以將長寬回報給父元件。

handleResize() {
  if (!this.textNode) return

  const width = this.textNode.getTextWidth()
  const height = this.textNode.getTextHegiht()

  if (this.width !== width || this.height !== height) {
    const { onResize } = this.props
    if (typeof onResize === 'function') {
      onResize(width, height)
    }
    this.width = width
    this.height = height
  }
}

當你想畫個剛好可以容納文字的容器時,靠 onResize 取得第一次畫的文字長寬,再畫一次外面的容器即可。因為第二次畫的 Text 長寬不變, onResize 不會再被呼叫一次,不用擔心遞迴呼叫停不下來。

Text 畫完後,再根據其長寬畫外框
render() {
  const { w, h } = this.state
  const width = w + 2 * this.padding.vertical
  const height = h + 2 * this.padding.horizontal

  return (
    <Group>
      <Rect
        width={width}
        height={height}
        fill="red"
        cornerRadius={6}
      />
      <Text
        x={this.padding.vertical}
        y={this.padding.horizontal}
        fontSize={32}
        text="much better"
        onResize={(w, h) => this.setState({ w, h })}
      />
    </Group>
  )
}

如果今天希望一列這樣的 Tag ,可以自動抓好彼此間的寬度與間距,該怎麼辦?為了讓父組件知道子組件的寬高變化,可以讓 Tag 也有自己的 onResize ,將寬高傳上去。

一個 Tag 長這樣

接著準備一個容器 TagList ,他會記下子組件送來的寬度,好重新排版。將子組件當成 children 傳遞給它時,不用提供 onResize ,由 TagList 自己以 cloneElement 插入。

以迴圈實作的 TagList

也可以遞迴實作這個 TagList 。

遞迴實作的 TagList

反過來說,可以先決定容器的寬度,再不斷修改內容物,直到所有子組件都能塞到容器內。

WidthBoundedText 專心把文字畫在指定寬度中

有了限制寬度的容器,可以做到文字換行。

一行 150px
Creative Commons License