这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
相关阅读
字体进阶
原生 konva 与 react-konva 中的不同点(坑)
在上一篇文章中双击修改文本的示例中,发现使用 state 驱动数据变化从而引起 Text 和 Transformer 组件引起变化,会使某些功能失效,比如,我们在键入文本改变了 Text 长度的时候,Transformer 组件并没有随其动态改变宽高。
这里的解决方法是,使用 state 去驱动 ref,调用 ref.current 上的 konva 方法.
如果使用 state 控制一个元素的显示与隐藏,如下:
useEffect(() => {
if (trRef) {
const tr = trRef.current
if (isSelected && showTransformer) {
tr.show()
tr.forceUpdate()
} else {
tr.hide()
}
}
}, [isSelected, showTransformer, trRef])
效果如下:
总之,在 react-konva 中控制元素的显隐,一定不能用 state 直接绑到视图层!!
字体修改
和上面修改样式一样,我们只要改变fontFamily属性就可以修改Text的字体了。(当然,前提是你的项目中已经有这种字体)
// 画布组件
useEffect(() => {
if (
selectedItemChange &&
Object.keys(selectedItemChange).length &&
selectedId
) {
const index = infos.findIndex((i) => i.id === selectedId);
const selecteditem = infos[index];
const properties = {
...selecteditem,
...selectedItemChange,
};
const newInfos = [...infos];
newInfos.splice(index, 1, properties);
setInfo(newInfos);
}
}, [selectedItemChange]); // 更改选中元素的属性
// ...
<Stage width={width} height={height} ref={stageRef}>
{infos.map((i: Iinfo, idx: number) => (
<Layer key={i.id}>
// ...
</Layer>)}
</Stage>
使用
const fonts = [
{ name: 'fantasy', fontFamily: 'Fantasy' },
{ name: 'sans-serif', fontFamily: 'sans-serif' },
];
// ....
<YHSelect placeholder="请选择" onChange={changeFont}>
{fonts.map((f) => (
<Option value={f.fontFamily} key={f.name}>
{f.name}
</Option>
))}
</YHSelect>
<KonvaCanvas
// ...
selectedItemChange={changed}
/>
效果如下:
我的写法是,使用selectedItemChange字段来改变已选中元素的样式,你也可以设计更严谨的方式,这里不再详细说了。这一小节的主要问题是处理在线字体,也就是处理不存在本地/项目中的字体,是如何应用于画布上的。
在线字体渲染
这里的方案思考了很久,原本方案是直接让后端去生成含有所有字体的 css 文件,但觉得很不好,当用户从前端网页配置好一个新的字体后,这时服务端就要去写那个 css 文件;如果删掉一个字体,还要从 css 文件中去删么,这个思路显然不是最优解,后端不用生成这个 css 文件啊,只需要提供字体的地址和名称就可以了。
参考konva 官网文档中发现,可以用fontfaceobserver来处理字体文件的加载。当我们从后端中获取到字体数据后,就可以自己生成 css 文件了,把他插入到<head>中,然后再去调用这个 font。
那么这部分是怎么做的呢,我们先看下后端的数据
const fonts = [
{ name: 'fantasy', fontFamily: 'Fantasy' }, // 本地
{ name: 'sans-serif', fontFamily: 'sans-serif' }, // 本地
{
name: 'frutiger',
fontFamily: 'frutiger',
url: 'http://lib.mytac.cn/frutiger.ttf', // 远程,需要下载到本地
},
{
name: 'Blackletter',
fontFamily: 'Blackletter',
url: 'http://lib.mytac.cn/Blackletter.TTF', // 远程,需要下载到本地
},
]
远程的字体文件都有url字段,当我们选中某种在线字体后,需要构造一段<style>代码,里面包含定义字体的 font-face 相关部分的代码,我们用 id 来区分不同的字体样式,这样就不会插入相同的<style>元素了。(注意:当应用元素时,第一次加载字体是异步的,这里需要加下 loading,下面的代码没有补充)
const addFontFaceToCss = (fontFamily: string, url: string) => {
const id = 'fontStyle' + '_' + fontFamily
const styleElement = document.getElementById(id)
if (styleElement) {
setChanged({ fontFamily: fontFamily })
} else {
const el = document.createElement('style')
el.id = id
const str = `
@font-face {
font-family: ${fontFamily};
src: url(${url});}
` // 构造代码
el.innerText = str
document.head.append(el)
const font = new FontFaceObserver(fontFamily)
// 这里需要加一些loading
font.load()
.then(function () {
setChanged({ fontFamily: fontFamily })
console.log('Output Sans has loaded.')
})
.catch(function (err) {
console.log(err)
console.log('Output Sans failed to load.')
})
}
}