这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战
前言
公司要做一个在线制图的东西,这个需求砸到我身上时真的想哭,还好老天帮我,发现了 konva,官方文档也非常良心,不仅描述清晰,也有在线的示例,直接上手开发还是非常方便的。
所以我们拆解需求,一点一点来吧!
基础应用
在线制图最基础的应用是拖拽元素,比如,在画布上拖拽一张图片或某种形状,对该图片进行缩放或旋转操作。
画布就是<Stage>,每个图层为<Layer>。
拖拽元素
konva 中内置了很多形状的元素,比如圆形、矩形等,以下示例为“星型”,这里先用<Star>试一下:
import { Circle, Rect, Stage, Layer, Text, Star } from 'react-konva'
import Konva from 'konva'
const Shape = () => {
const [star, setStar] = useState({
x: 300,
y: 300,
rotation: 20,
isDragging: false,
})
const handleDragStart = () => {
setStar({
...star,
isDragging: true,
})
}
const handleDragEnd = (e: any) => {
setStar({
...star,
x: e.target.x(),
y: e.target.y(),
isDragging: false,
})
}
return (
<Stage width={1000} height={600}>
<Layer>
<Star
key="starid"
id="starid"
x={star.x}
y={star.y}
numPoints={5}
innerRadius={20}
outerRadius={40}
fill="#89b717"
opacity={0.8}
draggable
rotation={star.rotation}
shadowColor="black"
shadowBlur={10}
shadowOpacity={0.6}
shadowOffsetX={star.isDragging ? 10 : 5}
shadowOffsetY={star.isDragging ? 10 : 5}
scaleX={star.isDragging ? 1.2 : 1}
scaleY={star.isDragging ? 1.2 : 1}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
/>
</Layer>
</Stage>
)
}
其中,可以给 Star 配置一些基础的属性,如:x、y 指该元素在画布上的坐标位置,rotaition 指元素的旋转角度;fill 指元素的填充颜色,scaleX、scaleY 指元素在 x、y 轴上的放大比例等等。
在拖拽的时候,我们要给该元素添加一些拖拽事件,如上:添加 handleDragStart 更改isDragging属性,使其在拖动时产生形变;添加 onDragEnd 事件,更改isDragging和 x、y 属性,来改变拖动位置,关闭拖动形变特效等。
观察上面的代码发现某些属性和"react-dnd"类似,但在使用 drag 事件的时候,发现比 react-dnd 方便很多,可能因为底层是 canvas 的原因吧!
导入图片
有两种方式可以导入图片,一个是用 react-hooks,一个是调用 react 生命周期函数,这里为了图省事,用 hooks:
先安装 konva 的官方库use-image,之后我们来封装一下图片组件:
import { Image } from 'react-konva'
import useImage from 'use-image'
const KonvaImage = ({ url = '' }) => {
const [image] = useImage(url)
return <Image image={image} />
}
export default KonvaImage
变形
使元素变形,需要引用 konva 的Transformer组件,该组件可以使元素的缩放、旋转。如下代码,在选中某元素后,会展示 Transformer 组件,在该组件上存在boundBoxFunc属性,当用户触发元素的变形行为时,该函数会被调用,返回一个包含形变后元素的信息(下面代码中为 newBox)。
import React, { useState, useEffect, useRef } from 'react'
import { Image, Transformer } from 'react-konva'
import Konva from 'konva'
import useImage from 'use-image'
const KonvaImage = ({ url = '', isSelected = false }) => {
const [image] = useImage(url)
const imgRef = useRef()
const trRef = useRef()
useEffect(() => {
if (isSelected) {
trRef.current.nodes([imgRef.current])
trRef.current.getLayer().batchDraw()
}
}, [isSelected])
return (
<>
<Image image={image} draggable ref={imgRef} />
{isSelected && (
<Transformer
ref={trRef}
boundBoxFunc={(oldBox, newBox) => {
// limit resize
if (newBox.width < 5 || newBox.height < 5) {
return oldBox
}
const { width, height } = newBox
// console.log('width', width);
// console.log('height', height);
return newBox
}}
/>
)}
</>
)
}
export default KonvaImage
合成图片
在 Stage 上添加 ref,会把画布输出 base64,之后转为图片在浏览器中触发下载行为。
function downloadURI(uri: string, name: string) {
var link = document.createElement('a');
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
const exportToImage = () => {
const uri = stageRef.current.toDataURL();
downloadURI(uri, 'stage.png');
};
....
<button onClick={exportToImage}>export</button>
<Stage width={1200} height={1000} ref={stageRef}>
这里,注意图片的跨域问题,如果图片地址跨域了,图片的 konva 组件不会显示,所以要给图片服务器设置下 cors 头,或是中间做一层转发。并且要在代码层添加 crossorigin 属性开启 cors。否则就会在 canvas 的 toBlob()、toDataURL()和 getImageData()上报错。
图片跨域设置
konva 封装好的use-image提供好了跨域属性,如下
import useImage from 'use-image'
const [image, status] = useImage(url, 'anonymous')
<Image image={image} />
如果仍显示跨域问题不能生成图片,需要在服务器端添加跨域头或者做一层转发了。