caasih.net

React Konva

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

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

RectText 彼此無關

可以給 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}
  />
)

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

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