使用react-konva制作在线制图应用(8)——字体的加粗、斜体、对齐、透明、旋转

1,316 阅读2分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

前置阅读

  1. 使用konva制作在线photoshop(1)——元素拖拽、变形与导出
  2. 使用react-konva制作在线photoshop(2)——字体的文本与样式的修改
  3. 使用react-konva制作在线制图应用(3)——在线字体文件的动态渲染
  4. 使用react-konva制作在线制图应用(4)——撤销/重做(踩坑篇)
  5. 使用react-konva制作在线制图应用(5)——撤销/重做(填坑篇)
  6. 使用react-konva制作在线制图应用(6)——图层、缩放画布、删除元素
  7. 使用react-konva制作在线制图应用(7)——用户改变字体宽高和颜色

改变字体加粗斜体样式

我们上文中提过字体的加粗和斜体的展示,通过修改fontStyle这个属性就可以实现;给字体加下划线是改变textDecoration这个属性,代码如下:

    const isBold = fontStyle.includes('bold')
    const isUnderline = textDecoration.includes('underline')
    const isItalic = fontStyle.includes('italic')

    const onChange = (key: string, isExsit: boolean) => {
        const isDecor = key === 'underline'
        let s: string = isDecor ? textDecoration : fontStyle

        if (isExsit) {
            // 存在就删除
            const reg = new RegExp(key)
            s = s.replace(reg, '')
        } else {
            // 不存在就加上
            s += ' ' + key
        }
        onChangeFontStyle({ [isDecor ? 'textDecoration' : 'fontStyle']: s })
    }
    
    // ...
    
                <div
                    className={`button1${isBold ? ' selected' : ''}`}
                    onClick={onChange.bind(null, 'bold', isBold)}
                >
                    <BoldOutlined />
                </div>
                <div
                    className={`button1${isUnderline ? ' selected' : ''}`}
                    onClick={onChange.bind(null, 'underline', isUnderline)}
                >
                    <UnderlineOutlined />
                </div>
                <div
                    className={`button1${isItalic ? ' selected' : ''}`}
                    onClick={onChange.bind(null, 'italic', isItalic)}
                >
                    <ItalicOutlined />
                </div>
    )
}

效果如下

demo

修改字体的对齐方式

在 konva 中字体的对齐方式的属性是align,值为:leftcenterright。控件的实现方式与上一段类似,只是多选与单选的区别。

    const onChange = (align: string) => {
        onChangeTextAlign({ align })
    }

    return (
            <div className="fontStyler">
                <div
                    className={`button1${
                        textAlign === 'left' ? ' selected' : ''
                    }`}
                    onClick={onChange.bind(null, 'left')}
                >
                    <AlignLeftOutlined />
                </div>
                <div
                    className={`button1${
                        textAlign === 'center' ? ' selected' : ''
                    }`}
                    onClick={onChange.bind(null, 'center')}
                >
                    <AlignCenterOutlined />
                </div>
                <div
                    className={`button1${
                        textAlign === 'right' ? ' selected' : ''
                    }`}
                    onClick={onChange.bind(null, 'right')}
                >
                    <AlignRightOutlined />
                </div>
            </div>
    )
}

效果如下:

字体的透明度调节

这里使用antd的 slider 对其进行控制,注意要用 1 减去当前滑动条的值。

!! 这里注意用 debounce 去封装一下 onChange 事件的函数,避免高频更改,使step队列中的无效存放。

    const [inputvalue, setInputValue] = useState(0)

    const confirmInput = (alpha: any) => {
        onChangeOpacity({ opacity: 1 - alpha / 100 })
    }

    const debounceConfirm = useCallback(debounce(confirmInput, 500), []) // 注意这里

    const onChangeSlide = (e: number) => {
        setInputValue(e)
        debounceConfirm(e)
    }

    useEffect(() => {
        setInputValue(Math.round((1 - opacity) * 100)) // opacity是0-1的小数
    }, [opacity])

// ...
    return (
                <YHSlider
                    min={0}
                    max={100}
                    onChange={onChangeSlide}
                    value={inputvalue}
                    step={1}
                />

                <YHInput
                    max={100}
                    min={0}
                    step={1}
                    type="number"
                    value={inputvalue}
                    suffix="%"
                    //@ts-ignore
                    onChange={(e) => onChangeSlide(e.target.value)}
                />
            </div>
    )
}

效果如下

旋转角度

实现方式与调节透明度几乎一致,改下参数和最值大小就 ok 了,修改的属性值为rotation


    const [inputvalue, setInputValue] = useState(0)

    const confirmInput = (deg: any) => {
        onChangeRotation({ rotation: Math.round(deg) })
    }
    const debounceConfirm = useCallback(debounce(confirmInput, 300), [])
    const onChangeSlide = (e: number) => {
        setInputValue(e)
        debounceConfirm(e)
    }

    useEffect(() => {
        setInputValue(Math.round(rotation))
    }, [rotation])

    return (
            <div className="slider">
                <YHSlider
                    min={-180}
                    max={180}
                    onChange={onChangeSlide}
                    value={inputvalue}
                    step={1}
                />

                <YHInput
                    min={-180}
                    max={180}
                    step={1}
                    type="number"
                    value={inputvalue}
                    suffix="°"
                    onChange={(e) => onChangeSlide(e.target.value)}
                />
    )

效果如下

旋转中心

如上图所示,元素的旋转并没有和我们设想的那样,随着元素的中心点旋转,而是围绕元素的左上角旋转(也就是 x、y)的位置,x、y 并没有变化。

查看官方文档 How to set rotation point of a shape?

需要我们自行计算旋转后的 x、y 值,我们改造下官方代码

const handleRotation = (rot: number) => {
    const topLeft = {
        x: -currentRef.width() / 2,
        y: -currentRef.height() / 2,
    }
    const current = rotatePoint(topLeft, Konva.getAngle(currentRef.rotation()))
    const rotated = rotatePoint(topLeft, Konva.getAngle(rot))
    const dx = rotated.x - current.x,
        dy = rotated.y - current.y

    return ({
        rotation: Math.round(rot),
        x: currentRef.x() + dx,
        y: currentRef.y() + dy,
    })
}

const confirmInput = (deg: any) => {
    const item = handleRotation(deg)
    onChangeRotation(item)
}

最终效果如图

大功告成,下章见!