React Konva
React Konva 將 Canvas 繪圖函式庫 Konva.js 包裝成 React components 。 Konva.js 則將 Canvas 操作包裝好,提供容器(例如 Group
)將形狀們包裝起來,方便位移、旋轉。
乍看很美好,但 Konva.js 不提供像 HTML 或 SVG 那樣的排版功能。
可以給 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
不會再被呼叫一次,不用擔心遞迴呼叫停不下來。
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
,將寬高傳上去。
接著準備一個容器 TagList
,他會記下子組件送來的寬度,好重新排版。將子組件當成 children
傳遞給它時,不用提供 onResize
,由 TagList
自己以 cloneElement
插入。
也可以遞迴實作這個 TagList
。
反過來說,可以先決定容器的寬度,再不斷修改內容物,直到所有子組件都能塞到容器內。
有了限制寬度的容器,可以做到文字換行。