width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const rect1 = new Konva.Rect({
x: 20,
y: 20,
width: 100,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 4
});
layer.add(rect1);
const rect2 = new Konva.Rect({
x: 150,
y: 40,
width: 100,
height: 50,
fill: 'red',
shadowBlur: 10,
cornerRadius: 10
});
layer.add(rect2);
const rect3 = new Konva.Rect({
x: 50,
y: 120,
width: 100,
height: 100,
fill: 'blue',
cornerRadius: [0, 10, 20, 30]
});
layer.add(rect3);
stage.add(layer);
```
```js
import { Stage, Layer, Rect } from 'react-konva';
const App = () => {
return (
);
};
export default App;
```
```js
```
**后续步骤:**
- [为图形添加事件 →](/zh-Hans/docs/events/Binding_Events.html)
- [使图形可拖动 →](/zh-Hans/docs/drag_and_drop/Drag_and_Drop.html)
- [添加调整尺寸和旋转手柄 →](/zh-Hans/docs/select_and_transform/Basic_demo.html)
- [为图形属性添加动画 →](/zh-Hans/docs/animations/Create_an_Animation.html)
- [Konva.Rect API 参考 →](/zh-Hans/api/Konva.Rect.html)
---
# HTML5 Canvas 图形事件
> 学习如何使用 Konva.js 为 HTML5 Canvas 图形绑定事件监听器,以及如何处理 click、dblclick、mouseover、mouseout、mousemove 等事件。
Source: https://konvajs.org/zh-Hans/docs/events/Binding_Events.html
要使用 Konva 检测图形事件,可以使用 `on()` 方法将事件处理程序绑定到节点。
`on()` 方法需要一个事件类型,以及一个在事件发生时执行的函数。
鼠标事件:`mouseover`、`mouseout`、`mouseenter`、`mouseleave`、`mousemove`、`mousedown`、`mouseup`、`wheel`、`click`、`dblclick`。
触摸事件:`touchstart`、`touchmove`、`touchend`、`tap`、`dbltap`。
指针事件:`pointerdown`、`pointermove`、`pointereup`、`pointercancel`、`pointerover`、`pointerenter`、`pointerout`、`pointerleave`、`pointerclick`、`pointerdblclick`。
拖放事件:`dragstart`、`dragmove` 和 `dragend`。
变换事件:`transformstart`、`transform`、`transformend`。
**操作说明:在三角形上触发 mouseover 和 mouseout,并在圆形上触发 mouseover、mouseout、mousedown 和 mouseup。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
const triangle = new Konva.RegularPolygon({
x: 80,
y: 120,
sides: 3,
radius: 80,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
});
const circle = new Konva.Circle({
x: 230,
y: 100,
radius: 60,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
function writeMessage(message) {
text.text(message);
}
triangle.on('mouseout', () => {
writeMessage('Mouseout triangle');
});
triangle.on('mousemove', () => {
const mousePos = stage.getPointerPosition();
writeMessage('x: ' + mousePos.x + ', y: ' + mousePos.y);
});
circle.on('mouseover', () => {
writeMessage('Mouseover circle');
});
circle.on('mouseout', () => {
writeMessage('Mouseout circle');
});
circle.on('mousedown', () => {
writeMessage('Mousedown circle');
});
circle.on('mouseup', () => {
writeMessage('Mouseup circle');
});
layer.add(triangle);
layer.add(circle);
layer.add(text);
stage.add(layer);
````
```jsx
import { Stage, Layer, RegularPolygon, Circle, Text } from 'react-konva';
import { useRef, useState } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const stageRef = useRef();
const writeMessage = (text) => {
setMessage(text);
};
return (
writeMessage('Mouseout triangle')}
onMousemove={() => {
const mousePos = stageRef.current.getPointerPosition();
writeMessage('x: ' + mousePos.x + ', y: ' + mousePos.y);
}}
/>
writeMessage('Mouseover circle')}
onMouseout={() => writeMessage('Mouseout circle')}
onMousedown={() => writeMessage('Mousedown circle')}
onMouseup={() => writeMessage('Mouseup circle')}
/>
);
};
export default App;
````
```html
```
---
# HTML5 Canvas 拖放教程
> 学习使用 Konva.js 为 HTML5 Canvas 图形添加拖放功能。通过一个属性使任意图形可以拖动,并处理拖动事件。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drag_and_Drop.html
如需使用 Konva 拖放图形,请在创建图形时将 `draggable` 属性设置为 true。你也可以使用 `draggable()` 方法。
`draggable()` 方法会自动为桌面应用和移动应用启用拖放功能。
如需检测 Konva 拖放事件,请使用 `on()` 方法将 `dragstart`、`dragmove` 或 `dragend` 事件绑定到节点。
`on()` 方法需要事件类型和回调函数。事件发生时,Konva 会运行该函数。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true,
});
// add cursor styling
circle.on('mouseover', function () {
document.body.style.cursor = 'pointer';
});
circle.on('mouseout', function () {
document.body.style.cursor = 'default';
});
layer.add(circle);
```
```jsx
import { Stage, Layer, Circle } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [position, setPosition] = useState({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
return (
{
document.body.style.cursor = 'pointer';
}}
onMouseLeave={(e) => {
document.body.style.cursor = 'default';
}}
onDragEnd={(e) => {
setPosition({
x: e.target.x(),
y: e.target.y()
});
}}
/>
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas Konva 动画教程
> 学习如何使用 Konva.js 在 HTML5 Canvas 上创建动画。使用 Konva.Animation 创建逐帧动画,使用 Konva.Tween 实现属性过渡。
Source: https://konvajs.org/zh-Hans/docs/animations/Create_an_Animation.html
如需使用 Konva 创建自定义动画,可以使用 `Konva.Animation`
构造函数。此构造函数接受两个参数:必需的更新函数,以及
可选的图层或图层数组。每个动画帧都会更新这些图层。
动画函数会收到一个 `frame` 对象。其 `time` 属性表示动画已经运行的
毫秒数。`timeDiff` 属性表示自上一帧以来经过的
毫秒数。`frameRate` 属性表示当前帧率,
单位为每秒帧数。
更新函数绝不能重绘舞台或图层,因为动画引擎会自动处理重绘。
更新函数应仅包含更新节点属性的逻辑,
例如 `position`、`rotation`、`scale`、`width`、`height`、`radius` 和 `colors` 等。
创建动画后,可以随时使用 `start()` 方法启动它。
如需查看完整的属性和方法列表,请参阅 [Konva.Animation 文档](/zh-Hans/api/Konva.Animation.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const rect = new Konva.Rect({
x: 50,
y: 50,
width: 50,
height: 50,
fill: 'green',
});
layer.add(rect);
const anim = new Konva.Animation(function(frame) {
const time = frame.time;
const timeDiff = frame.timeDiff;
const frameRate = frame.frameRate;
// Example: move rectangle in a circle
const radius = 50;
const x = radius * Math.cos(frame.time * 2 * Math.PI / 2000) + 100;
const y = radius * Math.sin(frame.time * 2 * Math.PI / 2000) + 100;
rect.position({ x, y });
}, layer);
anim.start();
````
```js
import { Stage, Layer, Rect } from 'react-konva';
import { useEffect, useRef } from 'react';
const App = () => {
const rectRef = useRef(null);
useEffect(() => {
const anim = new Konva.Animation((frame) => {
const time = frame.time;
const timeDiff = frame.timeDiff;
const frameRate = frame.frameRate;
// Example: move rectangle in a circle
const radius = 50;
const x = radius * Math.cos(frame.time * 2 * Math.PI / 2000) + 100;
const y = radius * Math.sin(frame.time * 2 * Math.PI / 2000) + 100;
rectRef.current.position({ x, y });
}, rectRef.current.getLayer());
anim.start();
return () => {
anim.stop();
};
}, []);
return (
);
};
export default App;
````
```js
```
---
# HTML5 Canvas 图像模糊滤镜教程
> 学习如何使用 Konva.js 对 HTML5 Canvas 图像应用模糊滤镜,并通过 blurRadius 属性调整模糊程度。
Source: https://konvajs.org/zh-Hans/docs/filters/Blur.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 模糊图像,可以使用 `Konva.Filters.Blur` 滤镜
并通过 `blurRadius` 属性设置模糊程度。
**操作说明**:滑动控件以调整模糊半径。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Blur]);
image.blurRadius(10);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '40';
slider.value = image.blurRadius();
slider.style.position = 'absolute';
slider.style.top = '20px';
slider.style.left = '20px';
slider.addEventListener('input', (e) => {
const value = parseInt(e.target.value);
image.blurRadius(value);
});
document.body.appendChild(slider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [blurRadius, setBlurRadius] = useState(10);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Blur]}
blurRadius={blurRadius}
/>
setBlurRadius(parseInt(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas Konva 全部性能技巧列表
> Konva.js 的全部性能优化技巧,包括图层管理、图形缓存、禁用监听和批量绘制等内容。在 HTML5 Canvas 上高效渲染数千个图形。
Source: https://konvajs.org/zh-Hans/docs/performance/All_Performance_Tips.html
不想把时间花在性能问题上?申请[性能评审](https://lavrton.com/consulting/)。
### 为什么这很重要
HTML5 Canvas 可以高效完成绘制工作,`Konva` 内部也有许多用于提高性能的功能。但是,随着项目复杂度增加或舞台上的图形数量增多,性能必然会受到影响。
### 优化目标
这里的优化主要遵循两条通用规则:
* 尽量减少计算:所有计算都需要时间。单次计算可能只需极短时间,但你的代码、Konva、JavaScript 和更底层代码产生的数千或数百万次计算会不断累积。如果原本流畅的动画或效果出现卡顿,人眼就能察觉这种影响。
* **尽量减少绘制**:所有绘制都有性能成本,因此这条规则很重要。成本分为两类:一类是上一点介绍的绘制计算,另一类是将绘制内容从内存传输到屏幕。某些情况下,还会有中间的离屏合成或逐像素处理。因此,请尽量减少绘制。
### 舞台
1. 优化舞台尺寸——按照“尽量减少绘制”的规则,请避免创建大型舞台。将大量字节从内存传输到屏幕会降低性能。[此处](/zh-Hans/docs/sandbox/Canvas_Scrolling.html)提供了一些解决超大舞台问题的替代方法。
2. 在移动端设置视口——缩放图像会明显降低性能。因此,请为移动端应用设置视口:`
`。这样可以避免对 Konva 输出进行不必要的缩放。
3. 在 Retina 设备上使用 `Konva.pixelRatio = 1`——Konva 会自动调整像素比,从而在所有设备上清晰渲染。如果 Retina 设备的性能不佳,请设置 `Konva.pixelRatio = 1`,以减少 Konva 的缩放工作。在某些情况下,此设置可能影响输出,因此请确认结果质量符合要求。
### 图层
1. [图层管理](/zh-Hans/docs/performance/Layer_Management.html)——在内部,每个 Konva 图层都是一个单独的 HTML5 Canvas 元素。这样可以只刷新发生变化的图层,从而避免刷新整个舞台的性能成本。但是,每个图层都会增加性能开销,因此应尽量减少图层数量。
2. 使用 `layer.listening(false)`——Konva 为绘制的所有图形提供鼠标和触摸事件监听器。每个监听器都有性能成本。对于包含许多图形的图层,Konva 必须花费大量周期检查可能触发的监听器。如果图层中的所有图形都不需要响应事件,请设置 `layer.listening(false)` 以消除此开销。请参阅[示例](/zh-Hans/docs/sandbox/Animation_Stress_Test.html)。图形部分也有类似技巧。
3. 优化拖动成本——在图层上拖动图形时,每次移动事件循环都必须重绘该图层。为避免此性能成本,请在拖动时将图形移动到专用图层,并在拖动结束时将其移回原图层。请参阅[示例](/zh-Hans/docs/sandbox/Drag_and_Drop_Stress_Test.html)
### 图形
1. [图形缓存](/zh-Hans/docs/performance/Shape_Caching.html)——Konva 在内部创建图形的图像,并在需要绘制图形时使用该图像。绘制图像可以避免按照绘制指令组合图形的开销,并能明显提高复杂图形和组的性能。
2. 整理图形——舞台中的每个图形都会产生存在成本。要优化性能,请隐藏或从图层中移除不可见、不透明度为 0 或移出视野的对象。
3. 使用 `shape.listening(false)`——与图层一样(请参阅上文第 7 点),Konva 会检查何时应为图形触发事件,这会产生性能成本。让图形停止监听事件可以降低此成本,详情请参阅[禁用监听](/zh-Hans/docs/performance/Listening_False.html)。
4. 禁用完美绘制——在某些情况下,HTML5 Canvas 的绘制结果可能与预期不同。有关示例,请参阅[禁用完美绘制](/zh-Hans/docs/performance/Disable_Perfect_Draw.html)。Konva 使用完美绘制功能执行额外工作以修正结果,但这会产生性能成本。当图形同时具有填充、描边和不透明度时,设置 `shape.perfectDrawEnabled(false)` 可以避免此成本,并且不会降低输出质量。
5. [优化描边绘制](/zh-Hans/docs/performance/Optimize_Strokes.html)——当图形同时具有描边和阴影时,Konva 会执行一次额外的内部绘制,以获得符合预期的结果。禁用 Konva 为描边添加的阴影,可以避免此性能负担。
### 动画
1. [优化动画](/zh-Hans/docs/performance/Optimize_Animation.html)——避免为两次视觉变化之间的动画步骤执行不必要的重绘。
### 内存
1. [避免内存泄漏](/zh-Hans/docs/performance/Avoid_Memory_Leaks.html)——Konva 会处理许多可能产生内存泄漏的情况,但你仍需正确创建和移除图形与补间。
2. **了解一个图层的开销。** 每个图层都会分配两块 canvas:一块按设备像素比创建的场景 canvas,以及一块像素比始终为 1 的命中检测 canvas。在 Retina 屏幕上,一个 1920 × 1080 的舞台大约是 33 MB 加 8 MB,也就是在绘制任何内容之前每个图层约 41 MB。这正是 Konva 在图层超过五个时发出警告的原因——到那时,仅仅是空图层就已经接近 200 MB 的 canvas 内存。
3. **移动版 Safari 有硬性上限。** 超过之后会出现 `Total canvas memory use exceeds the maximum limit`,在部分设备上报告为 256 MB,在另一些设备上为 384 MB,而且 canvas 会直接变成空白,而不是逐步降级。按效果排序的手段是:减少图层数量、缩小舞台,以及设置 `Konva.pixelRatio = 1`——在 2× 屏幕上这会把场景 canvas 的开销降到四分之一。缓存也计入同一份预算,因此当节点不再可见时,请用 `node.clearCache()` 释放它。
### 超大场景
1. **剔除屏幕外的内容。** 无论节点是否落在舞台范围内,Konva 都会绘制图层上的每个节点。对于远大于视口的场景,请隐藏视口之外的节点——`visible(false)` 会同时跳过绘制和命中检测,比移除再重新添加节点便宜得多。
```js
function cull() {
const view = {
x: -stage.x() / stage.scaleX(),
y: -stage.y() / stage.scaleY(),
width: stage.width() / stage.scaleX(),
height: stage.height() / stage.scaleY(),
};
layer.children.forEach((node) => {
node.visible(Konva.Util.haveIntersection(view, node.getClientRect()));
});
}
```
请在视图发生变化时调用它——平移或缩放之后——而不是每一帧都调用。`getClientRect()` 并不便宜,因此对于数万个节点,请自行维护一份位置索引并基于它做判断,而不是逐个询问节点。
2. **拖动过程中跳过命中检测。** 当节点正在被拖动时,Konva 不会执行命中检测,这让拖动保持低开销。如果你需要在拖动过程中知道指针下方是什么——例如高亮放置目标——就重新开启它,并接受相应开销:
```js
Konva.hitOnDragEnabled = true; // default is false
```
3. **用一个图形代替许多图形。** 一千个各自拥有属性、变换和命中区域的节点,其开销远高于一个在单个 `sceneFunc` 中绘制一千个元素的自定义图形。代价是你会失去逐元素的事件与拖动,因此这是一种取舍,而不是白得的收益——参见[自定义图形](/zh-Hans/docs/shapes/Custom.html)。
以下示例展示了其中一些性能技巧的实际效果:
```js
import Konva from 'konva';
// Create stage with good performance settings
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
// Create layers with performance optimizations
const backgroundLayer = new Konva.Layer({ listening: false });
const mainLayer = new Konva.Layer();
const dragLayer = new Konva.Layer();
stage.add(backgroundLayer);
stage.add(mainLayer);
stage.add(dragLayer);
// Create a shape with caching
const star = new Konva.Star({
x: 200,
y: 200,
numPoints: 6,
innerRadius: 40,
outerRadius: 70,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
draggable: true,
perfectDrawEnabled: false, // performance optimization
});
// Cache the shape for better performance
star.cache();
// Optimize dragging performance
star.on('dragstart', () => {
star.moveTo(dragLayer);
});
star.on('dragend', () => {
star.moveTo(mainLayer);
});
// Create background with listening disabled
const rect = new Konva.Rect({
x: 0,
y: 0,
width: stage.width(),
height: stage.height(),
fill: 'lightgray',
listening: false,
});
backgroundLayer.add(rect);
mainLayer.add(star);
```
```js
import { Stage, Layer, Star, Rect } from 'react-konva';
import { Portal } from 'react-konva-utils';
import { useState, useRef, useEffect } from 'react';
const App = () => {
const [isDragging, setIsDragging] = useState(false);
const [position, setPosition] = useState({ x: 200, y: 200 });
const starRef = useRef(null);
useEffect(() => {
// Cache the shape for better performance
if (starRef.current) {
starRef.current.cache();
}
}, []);
const handleDragStart = () => {
setIsDragging(true);
};
const handleDragEnd = (event) => {
setPosition(event.target.position());
setIsDragging(false);
};
return (
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 舞台序列化教程
> 学习如何使用 Konva.js 将 HTML5 Canvas 状态序列化并保存为 JSON,以及如何使用 stage.toJSON() 导出并通过 Konva.Node.create() 恢复 Canvas 内容。
Source: https://konvajs.org/zh-Hans/docs/data_and_serialization/Serialize_a_Stage.html
要使用 Konva 序列化舞台,可以使用 `toJSON()` 方法。
`toJSON()` 方法返回一个包含节点所有属性的 JSON 字符串。
事件处理程序和图像无法序列化。
```js
import Konva from 'konva';
// Create wrapper with relative positioning
const stage = new Konva.Stage({
container: 'container',
width: 400,
height: 400
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: 100,
y: 100,
radius: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 3
});
layer.add(circle);
// Add button on top of stage
const button = document.createElement('button');
button.textContent = 'Serialize Stage';
button.style.position = 'absolute';
button.style.top = '10px';
button.style.left = '10px';
document.body.appendChild(button);
button.addEventListener('click', () => {
const json = stage.toJSON();
console.log(json);
alert('Stage serialized! Check the console for the JSON string.');
});
```
**注意:**虽然可以在 React 中直接序列化舞台,但这通常是一种反模式。在 React 应用中,应单独管理应用状态,并序列化该状态而不是舞台。
```js
import { Stage, Layer, Circle } from 'react-konva';
import { useRef, useState } from 'react';
const App = () => {
const stageRef = useRef(null);
const [circle, setCircle] = useState({
x: 100,
y: 100,
radius: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 3
});
const handleSerialize = () => {
// In a real app, prefer saving app state, not stage JSON
const json = JSON.stringify({ shapes: [circle] });
console.log('Serialized state:', json);
alert('State serialized! Check the console for the JSON string.');
};
return (
{
setCircle({
...circle,
x: e.target.x(),
y: e.target.y()
});
}}
/>
);
};
export default App;
```
**注意:**虽然可以在 Vue 中直接序列化舞台,但这通常是一种反模式。在 Vue 应用中,应使用响应式数据管理应用状态,并序列化该状态而不是舞台。
```js
```
---
# HTML5 Canvas 图形选择、调整尺寸和旋转
> 学习如何使用 Konva.js Transformer 选择、调整 HTML5 Canvas 图形的尺寸并旋转图形。为任意图形添加可交互的调整尺寸和旋转手柄。
Source: https://konvajs.org/zh-Hans/docs/select_and_transform/Basic_demo.html
`Transformer` 是一种特殊的 `Konva.Group`。使用它可以调整任意节点或一组节点的尺寸并旋转这些节点。
要启用它,请执行以下操作:
1. 使用 `new Konva.Transformer()` 创建新实例
2. 将它添加到图层
3. 使用 `transformer.nodes([shape]);` 将它附加到节点
_注意:_ 调整节点尺寸时,变换工具不会更改节点的 `width` 和 `height` 属性。它会改为更改 `scaleX` 和 `scaleY` 属性。
**操作说明:尝试调整图形尺寸并旋转图形。单击空白区域可取消选择。使用 SHIFT 或 CTRL 将图形添加到选区或从选区移除。尝试在 Canvas 上框选一个区域。**
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
// create rectangle
const rect1 = new Konva.Rect({
x: 60,
y: 60,
width: 100,
height: 90,
fill: 'red',
name: 'rect',
draggable: true,
});
layer.add(rect1);
const rect2 = new Konva.Rect({
x: 250,
y: 100,
width: 150,
height: 90,
fill: 'green',
name: 'rect',
draggable: true,
});
layer.add(rect2);
// create transformer
const tr = new Konva.Transformer();
layer.add(tr);
// add a new feature, lets add ability to draw selection rectangle
let selectionRectangle = new Konva.Rect({
fill: 'rgba(0,0,255,0.5)',
visible: false,
});
layer.add(selectionRectangle);
let x1, y1, x2, y2;
stage.on('mousedown touchstart', (e) => {
// do nothing if we mousedown on any shape
if (e.target !== stage) {
return;
}
x1 = stage.getPointerPosition().x;
y1 = stage.getPointerPosition().y;
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;
selectionRectangle.setAttrs({
x: x1,
y: y1,
width: 0,
height: 0,
visible: true,
});
});
stage.on('mousemove touchmove', () => {
// do nothing if we didn't start selection
if (!selectionRectangle.visible()) {
return;
}
x2 = stage.getPointerPosition().x;
y2 = stage.getPointerPosition().y;
selectionRectangle.setAttrs({
x: Math.min(x1, x2),
y: Math.min(y1, y2),
width: Math.abs(x2 - x1),
height: Math.abs(y2 - y1),
});
});
stage.on('mouseup touchend', () => {
// do nothing if we didn't start selection
if (!selectionRectangle.visible()) {
return;
}
// update visibility in timeout, so we can check it in click event
setTimeout(() => {
selectionRectangle.visible(false);
});
var shapes = stage.find('.rect');
var box = selectionRectangle.getClientRect();
var selected = shapes.filter((shape) =>
Konva.Util.haveIntersection(box, shape.getClientRect())
);
tr.nodes(selected);
});
// clicks should select/deselect shapes
stage.on('click tap', function (e) {
// if we are selecting with rect, do nothing
if (selectionRectangle.visible() && selectionRectangle.width() > 0 && selectionRectangle.height() > 0) {
return;
}
// if click on empty area - remove all selections
if (e.target === stage) {
tr.nodes([]);
return;
}
// do nothing if clicked NOT on our rectangles
if (!e.target.hasName('rect')) {
return;
}
// do we pressed shift or ctrl?
const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
const isSelected = tr.nodes().indexOf(e.target) >= 0;
if (!metaPressed && !isSelected) {
// if no key pressed and the node is not selected
// select just one
tr.nodes([e.target]);
} else if (metaPressed && isSelected) {
// if we pressed keys and node was selected
// we need to remove it from selection:
const nodes = tr.nodes().slice(); // use slice to have new copy of array
// remove node from array
nodes.splice(nodes.indexOf(e.target), 1);
tr.nodes(nodes);
} else if (metaPressed && !isSelected) {
// add the node into selection
const nodes = tr.nodes().concat([e.target]);
tr.nodes(nodes);
}
});
````
```js
import { Stage, Layer, Rect, Transformer } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
const initialRectangles = [
{
x: 60,
y: 60,
width: 100,
height: 90,
fill: 'red',
id: 'rect1',
name: 'rect',
rotation: 0,
},
{
x: 250,
y: 100,
width: 150,
height: 90,
fill: 'green',
id: 'rect2',
name: 'rect',
rotation: 0,
},
];
// Helper functions for calculating bounding boxes of rotated rectangles
const degToRad = (angle) => (angle / 180) * Math.PI;
const getCorner = (pivotX, pivotY, diffX, diffY, angle) => {
const distance = Math.sqrt(diffX * diffX + diffY * diffY);
angle += Math.atan2(diffY, diffX);
const x = pivotX + distance * Math.cos(angle);
const y = pivotY + distance * Math.sin(angle);
return { x, y };
};
const getClientRect = (element) => {
const { x, y, width, height, rotation = 0 } = element;
const rad = degToRad(rotation);
const p1 = getCorner(x, y, 0, 0, rad);
const p2 = getCorner(x, y, width, 0, rad);
const p3 = getCorner(x, y, width, height, rad);
const p4 = getCorner(x, y, 0, height, rad);
const minX = Math.min(p1.x, p2.x, p3.x, p4.x);
const minY = Math.min(p1.y, p2.y, p3.y, p4.y);
const maxX = Math.max(p1.x, p2.x, p3.x, p4.x);
const maxY = Math.max(p1.y, p2.y, p3.y, p4.y);
return {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY,
};
};
const App = () => {
const [rectangles, setRectangles] = useState(initialRectangles);
const [selectedIds, setSelectedIds] = useState([]);
const [selectionRectangle, setSelectionRectangle] = useState({
visible: false,
x1: 0,
y1: 0,
x2: 0,
y2: 0,
});
const isSelecting = useRef(false);
const transformerRef = useRef();
const rectRefs = useRef(new Map());
// Update transformer when selection changes
useEffect(() => {
if (selectedIds.length && transformerRef.current) {
// Get the nodes from the refs Map
const nodes = selectedIds
.map(id => rectRefs.current.get(id))
.filter(node => node);
transformerRef.current.nodes(nodes);
} else if (transformerRef.current) {
// Clear selection
transformerRef.current.nodes([]);
}
}, [selectedIds]);
// Click handler for stage
const handleStageClick = (e) => {
// If we are selecting with rect, do nothing
// But allow point clicks through (when width/height are 0)
const selWidth = Math.abs(selectionRectangle.x2 - selectionRectangle.x1);
const selHeight = Math.abs(selectionRectangle.y2 - selectionRectangle.y1);
if (selectionRectangle.visible && selWidth > 0 && selHeight > 0) {
return;
}
// If click on empty area - remove all selections
if (e.target === e.target.getStage()) {
setSelectedIds([]);
return;
}
// Do nothing if clicked NOT on our rectangles
if (!e.target.hasName('rect')) {
return;
}
const clickedId = e.target.id();
// Do we pressed shift or ctrl?
const metaPressed = e.evt.shiftKey || e.evt.ctrlKey || e.evt.metaKey;
const isSelected = selectedIds.includes(clickedId);
if (!metaPressed && !isSelected) {
// If no key pressed and the node is not selected
// select just one
setSelectedIds([clickedId]);
} else if (metaPressed && isSelected) {
// If we pressed keys and node was selected
// we need to remove it from selection
setSelectedIds(selectedIds.filter(id => id !== clickedId));
} else if (metaPressed && !isSelected) {
// Add the node into selection
setSelectedIds([...selectedIds, clickedId]);
}
};
const handleMouseDown = (e) => {
// Do nothing if we mousedown on any shape
if (e.target !== e.target.getStage()) {
return;
}
// Start selection rectangle
isSelecting.current = true;
const pos = e.target.getStage().getPointerPosition();
setSelectionRectangle({
visible: true,
x1: pos.x,
y1: pos.y,
x2: pos.x,
y2: pos.y,
});
};
const handleMouseMove = (e) => {
// Do nothing if we didn't start selection
if (!isSelecting.current) {
return;
}
const pos = e.target.getStage().getPointerPosition();
setSelectionRectangle({
...selectionRectangle,
x2: pos.x,
y2: pos.y,
});
};
const handleMouseUp = () => {
// Do nothing if we didn't start selection
if (!isSelecting.current) {
return;
}
isSelecting.current = false;
// Update visibility in timeout, so we can check it in click event
setTimeout(() => {
setSelectionRectangle({
...selectionRectangle,
visible: false,
});
});
const selBox = {
x: Math.min(selectionRectangle.x1, selectionRectangle.x2),
y: Math.min(selectionRectangle.y1, selectionRectangle.y2),
width: Math.abs(selectionRectangle.x2 - selectionRectangle.x1),
height: Math.abs(selectionRectangle.y2 - selectionRectangle.y1),
};
// Only select shapes if selection box has actual size (not just a point click)
if (selBox.width > 0 && selBox.height > 0) {
const selected = rectangles.filter(rect => {
// Check if rectangle intersects with selection box
return Konva.Util.haveIntersection(selBox, getClientRect(rect));
});
setSelectedIds(selected.map(rect => rect.id));
}
};
const handleDragEnd = (e) => {
const id = e.target.id();
setRectangles(prevRects => {
const newRects = [...prevRects];
const index = newRects.findIndex(r => r.id === id);
if (index !== -1) {
newRects[index] = {
...newRects[index],
x: e.target.x(),
y: e.target.y()
};
}
return newRects;
});
};
const handleTransformEnd = (e) => {
// Find which rectangle(s) were transformed
const id = e.target.id();
const node = e.target;
setRectangles(prevRects => {
const newRects = [...prevRects];
// Update each transformed node
const index = newRects.findIndex(r => r.id === id);
if (index !== -1) {
const scaleX = node.scaleX();
const scaleY = node.scaleY();
// Reset scale
node.scaleX(1);
node.scaleY(1);
// Update the state with new values
newRects[index] = {
...newRects[index],
x: node.x(),
y: node.y(),
width: Math.max(5, node.width() * scaleX),
height: Math.max(5, node.height() * scaleY),
rotation: node.rotation(),
};
}
return newRects;
});
};
return (
{/* Render rectangles directly */}
{rectangles.map(rect => (
{
if (node) {
rectRefs.current.set(rect.id, node);
}
}}
onDragEnd={handleDragEnd}
onTransformEnd={handleTransformEnd}
/>
))}
{/* Single transformer for all selected shapes */}
{
// Limit resize
if (newBox.width < 5 || newBox.height < 5) {
return oldBox;
}
return newBox;
}}
/>
{/* Selection rectangle */}
{selectionRectangle.visible && (
)}
);
};
export default App;
```
```js
handleDragEnd(e, i)"
@transformend="(e) => handleTransformEnd(e, i)"
ref="rectRefs"
/>
```
## Transformer 不会做什么
`Transformer` 绘制控制手柄并应用缩放。对象吸附、对齐参考线、多选共用包围盒和各图形的宽高比规则都需要你自行实现。你可以参考[对象吸附](/zh-Hans/docs/sandbox/Objects_Snapping.html)中的方法。
生产环境中的编辑器除了 Transformer,还需要文本编辑、模板和导出功能。如果你不想自行实现这些功能,可以使用 [Polotno](https://polotno.com/?utm_source=konvajs&utm_medium=docs&utm_content=transformer-basic)。它是由 Konva 维护者基于 Konva 开发的商业设计编辑器 SDK,已提供这些功能。
---
# Node.js 配置
> 使用 canvas 或 skia-canvas 后端,在 Node.js 中配置 Konva 以进行服务器端渲染、图像生成和批处理。
Source: https://konvajs.org/zh-Hans/docs/nodejs/nodejs-setup/index.html
Konva 可以在 Node.js 环境中用于服务器端渲染、图像处理和 Canvas 操作。本指南帮助你在 Node.js 项目中配置 Konva。
## Konva 版本 10+
Konva v10+ 不再默认支持 Node.js 环境。现在需要显式导入 Canvas 后端。
### 安装
Konva v10+ 为 Node.js 提供两种后端选项:
**node-canvas 后端**:
```bash
npm install konva canvas
```
**Skia 后端(性能更好)**:
```bash
npm install konva skia-canvas
```
### 使用方法
导入 Konva 和所选后端:
**Canvas 后端**:
```js
import Konva from 'konva';
import 'konva/canvas-backend';
```
**Skia 后端**:
```js
import Konva from 'konva';
import 'konva/skia-backend';
```
**完整示例**:
```js
import Konva from 'konva';
import 'konva/canvas-backend'; // or 'konva/skia-backend'
// Create a stage
const stage = new Konva.Stage({
container: 'container', // This will be ignored in Node.js
width: 800,
height: 600
});
// ... the rest of your konva code
// Export as data URL
const dataURL = stage.toDataURL();
```
## Konva 版本 ≤ 9(旧版)
旧版 Konva 的配置更简单:
### 安装
```bash
npm install konva
```
### 配置
```js
const Konva = require('konva');
// Create a stage
const stage = new Konva.Stage({
container: 'container', // This will be ignored in Node.js
width: 800,
height: 600
});
// ... the rest of your konva code
// Export as data URL
const dataURL = stage.toDataURL();
```
## 服务器端渲染注意事项
在 Node.js 中使用 Konva 时,请注意以下事项:
1. **无 DOM**:Konva 不需要 DOM,因此非常适合服务器端渲染
2. **Canvas 导出**:使用 `stage.toDataURL()` 将 Canvas 导出为图像
3. **内存管理**:处理多个 Canvas 时,请注意内存用量
4. **性能**:Konva 在 Node.js 环境中执行批处理操作时性能良好
5. **SSR 框架**:对于 Next.js 和其他 SSR 框架,可以考虑仅在客户端渲染 Canvas 内容
## 常见使用场景
- **图像生成**:为电子邮件、报告或社交媒体创建动态图像
- **图表生成**:在服务器端生成图表
- **文档处理**:向 PDF 或其他文档添加图形
- **批处理**:并行处理多个图像或图形
---
# Konva.js 开发 AI 工具
> 使用 AI 更快地构建 Konva.js 应用。与通过 Konva 文档训练的 AI 机器人聊天,或将 Konva MCP 服务器连接到 Cursor、Claude Desktop、Windsurf 和其他 AI 编程工具。
Source: https://konvajs.org/zh-Hans/docs/ai_tools.html
## 使用 Konva 和 AI 编程
我们提供多种 AI 工具,帮助你更快地构建 Konva 应用。所有工具均由 [CrawlChat](https://www.crawlchat.com/) 提供支持。
AI 智能体会广泛使用 Konva 文档来回答问题。请注意,它是一个 LLM,与任何现代 LLM 一样,可能给出错误答案。
## AI 聊天机器人
单击任意页面上的“Ask AI”按钮,即可询问有关 Konva 的问题。
也可以加入 [Konva Discord 社区](https://discord.gg/8FqZwVT),并在那里询问 `@AiBot-CrawlChat`。
## MCP(模型上下文协议)
MCP 是一种将 AI 编程工具连接到外部文档的标准协议。Cursor、Claude Desktop 和 Windsurf 等工具通过 Konva MCP 服务器帮助你编写代码时,可以直接访问 Konva 文档。
### Cursor
将以下内容添加到 Cursor MCP 设置中:
**重要:Cursor 仅在“Agent”模式下使用 MCP。“Ask”和其他模式不会使用 MCP。**
```json
"konva-documentation": {
"command": "npx",
"args": [
"crawl-chat-mcp",
"--id=67d221efb4b9de65095a2579",
"--name=konva_documentation"
]
}
```
### Claude Desktop
将以下内容添加到 Claude Desktop 配置文件(`claude_desktop_config.json`)中:
```json
{
"mcpServers": {
"konva-documentation": {
"command": "npx",
"args": [
"crawl-chat-mcp",
"--id=67d221efb4b9de65095a2579",
"--name=konva_documentation"
]
}
}
}
```
在 macOS 上,配置文件位于 `~/Library/Application Support/Claude/claude_desktop_config.json`。在 Windows 上,配置文件位于 `%APPDATA%\Claude\claude_desktop_config.json`。
### Windsurf
将以下内容添加到 Windsurf MCP 配置中:
```json
"konva-documentation": {
"command": "npx",
"args": [
"crawl-chat-mcp",
"--id=67d221efb4b9de65095a2579",
"--name=konva_documentation"
]
}
```
### 通用 MCP 命令
对于任何兼容 MCP 的工具,请使用:
```
npx crawl-chat-mcp --id=67d221efb4b9de65095a2579 --name=konva_documentation
```
## LLM 可读文档
Konva 为 AI 工具提供机器可读的文档文件:
- [`/llms.txt`](/llms.txt) — Konva 的简要概述,包含主要文档链接(遵循 [llmstxt.org](https://llmstxt.org/) 标准)
- [`/llms-full.txt`](pathname:///zh-Hans/llms-full.txt) — 同一索引及所有文档页面的完整内容,适合通过单个文件读取文档的工具
- [`/llms-small.txt`](pathname:///zh-Hans/llms-small.txt) — 不包含沙盒示例的精简版本,便于放入单个模型的上下文
- [`
.md`](pathname:///zh-Hans/docs/overview.md) —— 每个文档页面都在相同路径下以 `.md` 扩展名提供纯 Markdown 版本
这些文件帮助 AI 助手准确回答有关 Konva 的问题。
如果你使用 [Polotno](https://polotno.com/?utm_source=konvajs&utm_medium=docs&utm_content=ai-tools) 构建设计编辑器,可以参阅其[使用 AI 构建](https://polotno.com/docs/build-with-ai?utm_source=konvajs&utm_medium=docs&utm_content=ai-tools)页面,获取对应的文档文件和 MCP 配置。Polotno 是由 Konva 维护者基于 Konva 开发的商业设计编辑器 SDK。
## 结合 Konva 使用 AI 的提示
向 AI 工具询问 Konva 时,可以通过以下方式获得更好的结果:
- 在提示词中明确提及“Konva”或“react-konva”
- 引用具体的 Konva 功能,例如“Transformer”“Layer”或“toDataURL”
- 每次询问一项任务,不要组合多个问题
- 对照 [Konva 文档](/zh-Hans/docs/overview.html)和 [API 参考](/zh-Hans/api/Konva.html)检查 AI 生成的代码
---
# 需要 Konva 库的相关帮助?
> 通过 StackOverflow、GitHub Issues、Discord 聊天、咨询和其他社区资源获取 Konva 帮助。
Source: https://konvajs.org/zh-Hans/docs/support.html
## 正在查找 Konva 框架的相关帮助?
可以使用以下方式:
1. 先在网上查找解决方案。搜索你的问题。可以使用 Google 或页面顶部的内置搜索功能。
2. [StackOverflow](https://stackoverflow.com/questions/tagged/konvajs) 是提问的最佳场所。提供在线示例、代码样本和正确标签等内容,创建高质量问题,更有可能获得好的答案。
3. 如果发现错误或想请求功能,请访问 [Issues 页面](https://github.com/konvajs/konva/issues)。
4. 如果只想讨论 Konva,可以加入 [Discord 聊天](https://discord.gg/8FqZwVT)。
5. 如果有值得分享的内容,请在 Twitter 上使用 `#konvajs` 标签。
6. 访问[更新日志](https://github.com/konvajs/konva/blob/master/CHANGELOG.md),了解最新变化。
7. 需要咨询或策略评审?请访问[咨询页面](https://lavrton.com/consulting/)。
8. 如果你需要完整的设计编辑器,而不是自定义画布,可以使用 [Polotno](https://polotno.com/?utm_source=konvajs&utm_medium=docs&utm_content=support)。它是由 Konva 维护者基于 Konva 开发的商业设计编辑器 SDK,并提供独立的文档和支持。
---
# Angular Konva 缓存教程
> 学习如何在 Angular 中使用 ng2-konva 缓存 Konva 图形,以提高 Canvas 渲染性能。
Source: https://konvajs.org/zh-Hans/docs/angular/Cache.html
缓存会将节点栅格化为内部图像。这可以提高性能,也是滤镜等某些效果的必要条件。
本示例在视图渲染后访问底层 Konva 节点,并手动调用 `cache()`。
**操作说明**:本示例显示一个带阴影样式的矩形。视图初始化后,该矩形会被缓存。
有关更多详细信息,请参阅 [Node API 参考](/zh-Hans/api/Konva.Node.html) 和 [`cache()` 文档](/zh-Hans/api/Konva.Node.html#cache)。
## 缓存示例
```js
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { RectConfig } from 'konva/lib/shapes/Rect';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App implements AfterViewInit {
@ViewChild('rect') rect!: any;
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configRect: RectConfig = {
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'red',
shadowBlur: 10,
shadowColor: 'black',
shadowOffsetX: 5,
shadowOffsetY: 5
};
ngAfterViewInit() {
if (this.rect) {
this.rect.getNode().cache();
}
}
}
```
---
# Angular Konva 自定义图形教程
> 学习如何在 Angular 中使用 ng2-konva 和 sceneFunc 绘制函数,在 Canvas 上绘制自定义图形。
Source: https://konvajs.org/zh-Hans/docs/angular/Custom_Shape.html
要使用 `ng2-konva` 创建自定义图形,请使用 `ko-shape` 组件并提供 `sceneFunc` 绘制函数。
在 `sceneFunc` 中,可以结合使用原生 Canvas 上下文和 `context.fillStrokeShape(shape)` 等 Konva 辅助方法,正确应用填充、描边和阴影样式。
**操作说明**:本示例使用 Canvas 路径命令绘制一个类似三角形的自定义图形。
有关更多详细信息,请参阅 [Shape API 参考](/zh-Hans/api/Konva.Shape.html)。
## 自定义图形示例
```js
import { Component } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { ShapeConfig } from 'konva/lib/shapes/Shape';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App {
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configShape: ShapeConfig = {
x: 100,
y: 100,
fill: 'red',
stroke: 'black',
strokeWidth: 2,
sceneFunc: (context: any, shape: any) => {
context.beginPath();
context.moveTo(0, 0);
context.lineTo(100, 0);
context.lineTo(50, 100);
context.closePath();
context.fillStrokeShape(shape);
}
};
}
```
---
# Angular Konva 拖放教程
> 学习如何在 Angular 中使用 ng2-konva 和事件处理程序,为 Canvas 图形实现拖放。
Source: https://konvajs.org/zh-Hans/docs/angular/Drag_And_Drop.html
要使图形可拖动,请在其配置中设置 `draggable: true`,并监听节点上的拖动事件。
开始拖动时,本示例会将被拖动的圆形移到其图层顶部。
**操作说明**:在舞台上拖动圆形。开始拖动时,观察它如何移到顶部。
有关更多详细信息,请参阅 [Node API 参考](/zh-Hans/api/Konva.Node.html) 和 [Circle API 参考](/zh-Hans/api/Konva.Circle.html)。
## 拖放示例
```js
import { Component } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { CircleConfig } from 'konva/lib/shapes/Circle';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App {
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configCircle: CircleConfig = {
x: 100,
y: 100,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true,
};
public handleDragstart(event: any): void {
event.target.moveToTop();
}
}
```
---
# Angular Konva 事件教程
> 学习如何在 Angular 中使用 ng2-konva 事件绑定,处理 Canvas 图形上的鼠标、触摸和指针事件。
Source: https://konvajs.org/zh-Hans/docs/angular/Events.html
要在 Angular 中处理指针事件,请将 `(mousemove)` 和 `(mouseout)` 等监听器直接附加到 `ng2-konva` 组件。
当鼠标移到三角形上时,本示例会根据舞台指针坐标更新文本标签。
**操作说明**:将鼠标移到三角形上以更新坐标标签。然后将鼠标移出,以重置文本。
有关更多详细信息,请参阅 [Node API 参考](/zh-Hans/api/Konva.Node.html)和 [Stage API 参考](/zh-Hans/api/Konva.Stage.html)。
## 事件示例
```js
import { Component } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { RegularPolygonConfig } from 'konva/lib/shapes/RegularPolygon';
import { TextConfig } from 'konva/lib/shapes/Text';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App {
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configTriangle: RegularPolygonConfig = {
x: 80,
y: 120,
sides: 3,
radius: 80,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4
};
public configText: TextConfig = {
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: 'hello',
fill: 'black'
};
public handleMouseMove(event: any): void {
const mousePos = event.target.getStage().getPointerPosition();
const x = mousePos.x - 190;
const y = mousePos.y - 40;
this.configText = { ...this.configText, text: 'x: ' + x + ', y: ' + y };
}
public handleMouseOut(): void {
this.configText = { ...this.configText, text: 'Mouseout triangle' };
}
}
```
---
# Angular Konva 滤镜教程
> 学习如何在 Angular 中配合缓存使用 ng2-konva,对 Canvas 图形应用模糊等视觉滤镜。
Source: https://konvajs.org/zh-Hans/docs/angular/Filters.html
要在 Konva 中应用滤镜,请设置 `filters` 属性,并在节点挂载后缓存该节点。
本示例对圆形使用模糊滤镜,并在底层 Konva 节点可用后调用 `cache()`。
**操作说明**:本示例渲染一个模糊的红色圆形。在编辑器中移除 `cache()`,以了解滤镜为何需要缓存。
有关更多详细信息,请参阅 [Filters API 参考](/zh-Hans/api/Konva.Filters.html) 和 [`cache()` 文档](/zh-Hans/api/Konva.Node.html#cache)。
## 滤镜示例
```js
import { Component, ViewChild } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { CircleConfig } from 'konva/lib/shapes/Circle';
import Konva from 'konva';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App {
@ViewChild('circle') circle!: any;
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configCircle: CircleConfig = {
x: 150,
y: 150,
radius: 50,
fill: 'red',
filters: [Konva.Filters.Blur],
blurRadius: 15
};
ngAfterViewInit() {
this.circle.getNode().cache({ offset: 10 });
}
}
```
---
# Angular Konva 图像教程
> 学习如何在 Angular 中使用 ng2-konva Image 组件,在 HTML5 Canvas 上加载并显示图像。
Source: https://konvajs.org/zh-Hans/docs/angular/Images.html
要在 Angular 中使用 `Konva` 显示图像,请使用 `ko-image`,并将已加载的 `HTMLImageElement` 分配给 `image` 属性。
Angular 默认使用 zoneless 模式,因此 signals 是从 `Image.onload` 等异步回调更新图像配置的最简单方法。
**操作说明**:本示例加载外部图像。浏览器完成加载后,示例会渲染该图像。
有关更多详细信息,请参阅 [Image API 参考](/zh-Hans/api/Konva.Image.html)。
## 图像示例
```js
import { Component, OnInit, signal } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { ImageConfig } from 'konva/lib/shapes/Image';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App implements OnInit {
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configImage = signal({
x: 50,
y: 50,
image: null,
width: 100,
height: 100
});
ngOnInit() {
const imageObj = new Image();
imageObj.onload = () => {
this.configImage.update((config) => ({
...config,
image: imageObj
}));
};
imageObj.src = 'https://konvajs.org/assets/yoda.jpg';
}
}
```
---
# Angular Konva 图形教程
> 学习如何在 Angular 中使用 ng2-konva 图形组件,在 Canvas 上绘制矩形、圆形、线条和文本。
Source: https://konvajs.org/zh-Hans/docs/angular/Shapes.html
所有 `ng2-konva` 图形组件都通过 `ko-` 前缀映射到 Konva 图形,因此可以通过 `config` 对象传递任何常规 Konva 图形配置。
本示例展示几个使用不同样式选项的常见图形,例如阴影、渐变和线条张力。
**操作说明**:本示例渲染文本、矩形、圆形,以及使用渐变填充的闭合线条。
有关更多详细信息,请参阅 [Konva API 参考](/zh-Hans/api/Konva.html)和 [Line API 参考](/zh-Hans/api/Konva.Line.html)。
## 图形示例
```js
import { Component } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { TextConfig } from 'konva/lib/shapes/Text';
import { RectConfig } from 'konva/lib/shapes/Rect';
import { CircleConfig } from 'konva/lib/shapes/Circle';
import { LineConfig } from 'konva/lib/shapes/Line';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App {
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configText: TextConfig = {
text: 'Some text on canvas',
fontSize: 15
};
public configRect: RectConfig = {
x: 20,
y: 50,
width: 100,
height: 100,
fill: 'red',
shadowBlur: 10
};
public configCircle: CircleConfig = {
x: 200,
y: 100,
radius: 50,
fill: 'green'
};
public configLine: LineConfig = {
x: 20,
y: 200,
points: [0, 0, 100, 0, 100, 100],
tension: 0.5,
closed: true,
stroke: 'black',
fillLinearGradientStartPoint: { x: -50, y: -50 },
fillLinearGradientEndPoint: { x: 50, y: 50 },
fillLinearGradientColorStops: [0, 'red', 1, 'yellow']
};
}
```
---
# Angular Konva 简单动画教程
> 学习如何在 Angular 中使用 ng2-konva 和 Konva.Animation 类创建简单的 Canvas 动画。
Source: https://konvajs.org/zh-Hans/docs/angular/Simple_Animations.html
Konva 提供两种主要动画工具:`node.to()` 用于简单过渡,`Konva.Animation` 用于逐帧更新。
视图初始化后,本示例直接使用 `Konva.Animation`,使圆形沿正弦波移动。
**操作说明**:本示例让红色圆形持续左右移动。
有关更多详细信息,请参阅 [动画文档](/zh-Hans/docs/animations/Rotation.html)和 [Node API 参考](/zh-Hans/api/Konva.Node.html)。
## 简单动画示例
```js
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { CircleConfig } from 'konva/lib/shapes/Circle';
import Konva from 'konva';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App implements OnInit, OnDestroy {
@ViewChild('circle') circle!: any;
private animation: any = null;
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configCircle: CircleConfig = {
x: 100,
y: 100,
radius: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 4
};
ngAfterViewInit() {
const circle = this.circle.getNode();
this.animation = new Konva.Animation((frame: any) => {
const time = frame.time;
const x = 100 + Math.sin(time / 1000) * 100;
circle.x(x);
}, circle.getLayer());
this.animation.start();
}
ngOnDestroy() {
if (this.animation) {
this.animation.stop();
}
}
}
```
---
# Angular Konva Transformer 教程
> 学习如何在 Angular 中使用 ng2-konva Transformer 进行交互式选择,以及调整 Canvas 图形尺寸并旋转图形。
Source: https://konvajs.org/zh-Hans/docs/angular/Transformer.html
Transformer 工具以命令式方式附加到节点。在 Angular 中,这意味着创建 `ko-transformer`,并在视图就绪后将它连接到所选图形。
**操作说明**:单击矩形以选择它,拖动矩形以移动它,然后单击空白舞台以清除选择。
有关更多详细信息,请参阅 [Transformer API 参考](/zh-Hans/api/Konva.Transformer.html)。
## Transformer 示例
```js
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { RectConfig } from 'konva/lib/shapes/Rect';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App implements AfterViewInit {
@ViewChild('rect') rect!: any;
@ViewChild('transformer') transformer!: any;
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configRect: RectConfig = {
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
};
ngAfterViewInit() {
this.transformer.getNode().nodes([this.rect.getNode()]);
}
public handleStageClick(event: any): void {
if (event?.target === event.target.getStage()) {
this.transformer.getNode().nodes([]);
}
}
public handleShapeClick(): void {
this.transformer.getNode().nodes([this.rect.getNode()]);
}
}
```
---
# Angular Konva 撤销/重做教程
> 学习如何在 Angular 中使用 ng2-konva 跟踪 state 历史记录,为 Canvas 交互实现撤销和重做。
Source: https://konvajs.org/zh-Hans/docs/angular/Undo-Redo.html
将 Canvas state 存储在普通数据对象中,而不是在每次渲染时从 Canvas 读回值,可使撤销和重做达到最佳效果。
本示例将矩形位置存储在小型历史记录栈中。单击撤销或重做时,示例会恢复之前的快照。
**操作说明**:拖动矩形以创建历史记录条目,然后单击撤销和重做以浏览已保存的 state。
有关更多详细信息,请参阅 [Rect API 参考](/zh-Hans/api/Konva.Rect.html)。
## 撤销/重做示例
```js
import { Component } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { RectConfig } from 'konva/lib/shapes/Rect';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App {
private history: RectConfig[] = [];
private currentIndex: number = -1;
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public configRect: RectConfig = {
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'red',
draggable: true
};
constructor() {
this.saveState();
}
private saveState(): void {
// Remove any states after current index
this.history = this.history.slice(0, this.currentIndex + 1);
// Add current state
this.history.push({ ...this.configRect });
this.currentIndex++;
}
public handleDragEnd(event: any): void {
this.configRect = {
...this.configRect,
x: event.target.x(),
y: event.target.y()
};
this.saveState();
}
public undo(): void {
if (this.canUndo()) {
this.currentIndex--;
this.configRect = { ...this.history[this.currentIndex] };
}
}
public redo(): void {
if (this.canRedo()) {
this.currentIndex++;
this.configRect = { ...this.history[this.currentIndex] };
}
}
public canUndo(): boolean {
return this.currentIndex > 0;
}
public canRedo(): boolean {
return this.currentIndex < this.history.length - 1;
}
}
```
## 手动构建历史记录的局限
上述历史记录每一步只记录一个值。生产级编辑器必须记录
成组操作,使多选拖动可以作为一个步骤撤销,还必须记录
变换,以及操作完成后才加载完的图像。该状态机
通常比绘图代码更庞大,因此请围绕文档操作而不是原始节点状态来设计历史记录。
---
# Angular Konva 层叠顺序教程
> 学习如何在 Angular 中使用 ng2-konva,通过管理数据数组控制图形层叠顺序和 z-index。
Source: https://konvajs.org/zh-Hans/docs/angular/zIndex.html
要在 Angular 中控制图形层叠顺序,请更新用于渲染图形的数据数组顺序。
本示例展示以下操作:
1. 创建一个包含随机位置和颜色的圆形图形数组
2. 处理拖动事件以更新图形的视觉顺序
3. 通过操作数组顺序保持正确的层叠顺序
4. 通过 Angular state 驱动渲染,而不是以命令式方式调用 `zIndex()`
**操作说明**:尝试拖动圆形。开始拖动时,它会自动移到层叠顶部。此示例通过操作数据中的圆形数组实现该效果,而不是手动更改 zIndex。
```js
import { Component, OnInit } from '@angular/core';
import { StageConfig } from 'konva/lib/Stage';
import { CircleConfig } from 'konva/lib/shapes/Circle';
import {
CoreShapeComponent,
StageComponent,
} from 'ng2-konva';
@Component({
selector: 'app-root',
standalone: true,
template: `
@for (item of items; track trackById($index, item)) {
}
`,
imports: [StageComponent, CoreShapeComponent],
})
export default class App implements OnInit {
public configStage: StageConfig = {
width: window.innerWidth,
height: window.innerHeight,
};
public items: CircleConfig[] = [];
private dragItemId: string | null = null;
ngOnInit() {
this.generateItems();
}
private generateItems(): void {
const newItems: CircleConfig[] = [];
for (let i = 0; i < 10; i++) {
newItems.push({
x: Math.random() * this.configStage.width!,
y: Math.random() * this.configStage.height!,
radius: 50,
id: "node-" + i,
fill: this.getRandomColor(),
draggable: true
});
}
this.items = newItems;
}
private getRandomColor(): string {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'];
return colors[Math.floor(Math.random() * colors.length)];
}
public handleDragstart(event: any): void {
this.dragItemId = event.target.id();
const item = this.items.find(i => i.id === this.dragItemId);
if (item) {
this.items = [
...this.items.filter((i) => i.id !== this.dragItemId),
item
];
}
}
public handleDragend(): void {
this.dragItemId = null;
}
public trackById(index: number, item: CircleConfig): string {
return item.id as string;
}
}
```
---
# 位置动画教程
> 学习如何使用 Konva.js 为 HTML5 Canvas 上的图形位置设置动画。使用 Konva.Animation 逐帧更新,使图形平滑移动。
Source: https://konvajs.org/zh-Hans/docs/animations/Moving.html
如需使用 Konva 为图形的位置设置动画,可以通过 `Konva.Animation` 创建新动画。
此动画会在每一帧修改图形的位置。
如需查看完整的属性和方法列表,请参阅 [Konva.Animation 文档](/zh-Hans/api/Konva.Animation.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: 50,
y: window.innerHeight / 2,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
layer.add(circle);
const amplitude = 100;
const period = 2000; // in milliseconds
const anim = new Konva.Animation(function(frame) {
circle.x(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
window.innerWidth / 2
);
}, layer);
anim.start();
````
```js
import { Stage, Layer, Circle } from 'react-konva';
import { useEffect, useRef } from 'react';
const App = () => {
const circleRef = useRef(null);
useEffect(() => {
const amplitude = 100;
const period = 2000; // in milliseconds
const anim = new Konva.Animation((frame) => {
circleRef.current.x(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
window.innerWidth / 2
);
}, circleRef.current.getLayer());
anim.start();
return () => {
anim.stop();
};
}, []);
return (
);
};
export default App;
````
```js
```
---
# 旋转动画教程
> 学习如何使用 Konva.js 为 HTML5 Canvas 上的图形旋转设置动画。使用 Konva.Animation 创建平滑的旋转动画。
Source: https://konvajs.org/zh-Hans/docs/animations/Rotation.html
如需使用 Konva 为图形的旋转设置动画,可以通过
`Konva.Animation` 创建新动画,并定义一个在每一帧修改图形旋转角度的函数。
本教程将使蓝色矩形绕左上角旋转,
黄色矩形绕中心旋转,并使红色矩形绕图形外部的一点旋转。
如需查看完整的属性和方法列表,请参阅 [Konva.Animation 文档](/zh-Hans/api/Konva.Animation.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// blue rectangle - rotate around top-left corner
const blueRect = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 50,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
offset: {
x: 0,
y: 0,
},
});
// yellow rectangle - rotate around center
const yellowRect = new Konva.Rect({
x: 200,
y: 50,
width: 100,
height: 50,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
offset: {
x: 50,
y: 25,
},
});
// red rectangle - rotate around point outside shape
const redRect = new Konva.Rect({
x: 350,
y: 50,
width: 100,
height: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
offset: {
x: -50,
y: 25,
},
});
layer.add(blueRect);
layer.add(yellowRect);
layer.add(redRect);
const angularSpeed = 90;
const anim = new Konva.Animation(function(frame) {
const angleDiff = (frame.timeDiff * angularSpeed) / 1000;
blueRect.rotate(angleDiff);
yellowRect.rotate(angleDiff);
redRect.rotate(angleDiff);
}, layer);
anim.start();
````
```js
import { Stage, Layer, Rect } from 'react-konva';
import { useEffect, useRef } from 'react';
const App = () => {
const blueRectRef = useRef(null);
const yellowRectRef = useRef(null);
const redRectRef = useRef(null);
useEffect(() => {
const angularSpeed = 90;
const anim = new Konva.Animation((frame) => {
const angleDiff = (frame.timeDiff * angularSpeed) / 1000;
blueRectRef.current.rotate(angleDiff);
yellowRectRef.current.rotate(angleDiff);
redRectRef.current.rotate(angleDiff);
}, blueRectRef.current.getLayer());
anim.start();
return () => {
anim.stop();
};
}, []);
return (
);
};
export default App;
````
```js
```
---
# HTML5 Canvas Konva 缩放动画教程
> 学习如何使用 Konva.Animation 以及 scaleX 和 scaleY 属性,为 HTML5 Canvas 上的图形缩放设置动画。
Source: https://konvajs.org/zh-Hans/docs/animations/Scaling.html
如需使用 Konva 为图形的缩放设置动画,可以通过
`Konva.Animation` 创建新动画,并定义一个在每一帧修改图形缩放比例的函数。
本教程将缩放蓝色六边形的 x 和 y 分量、黄色六边形的 y 分量,
以及红色六边形的 x 分量。红色六边形围绕位于图形右侧的轴缩放。
**操作说明:** 在六边形播放动画时拖放它们。
如需查看完整的属性和方法列表,请参阅 [Konva.Animation 文档](/zh-Hans/api/Konva.Animation.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// blue hexagon - scale x and y
const blueHex = new Konva.RegularPolygon({
x: 50,
y: 50,
sides: 6,
radius: 20,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
// yellow hexagon - scale y only
const yellowHex = new Konva.RegularPolygon({
x: 150,
y: 50,
sides: 6,
radius: 20,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
// red hexagon - scale x only
const redHex = new Konva.RegularPolygon({
x: 250,
y: 50,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
layer.add(blueHex);
layer.add(yellowHex);
layer.add(redHex);
const period = 2000;
const anim = new Konva.Animation(function(frame) {
const scale = Math.sin(frame.time * 2 * Math.PI / period) + 2;
// blue hex - scale x and y
blueHex.scale({ x: scale, y: scale });
// yellow hex - scale y only
yellowHex.scaleY(scale);
// red hex - scale x only
redHex.scaleX(scale);
}, layer);
anim.start();
````
```js
import { Stage, Layer, RegularPolygon } from 'react-konva';
import { useEffect, useRef, useState } from 'react';
const App = () => {
const blueHexRef = useRef(null);
const yellowHexRef = useRef(null);
const redHexRef = useRef(null);
const [positions, setPositions] = useState({
blue: { x: 50, y: 50 },
yellow: { x: 150, y: 50 },
red: { x: 250, y: 50 }
});
useEffect(() => {
const period = 2000;
const anim = new Konva.Animation((frame) => {
const scale = Math.sin(frame.time * 2 * Math.PI / period) + 2;
// blue hex - scale x and y
blueHexRef.current.scale({ x: scale, y: scale });
// yellow hex - scale y only
yellowHexRef.current.scaleY(scale);
// red hex - scale x only
redHexRef.current.scaleX(scale);
}, blueHexRef.current.getLayer());
anim.start();
return () => {
anim.stop();
};
}, []);
const handleDragEnd = (e, color) => {
setPositions(prev => ({
...prev,
[color]: { x: e.target.x(), y: e.target.y() }
}));
};
return (
handleDragEnd(e, 'blue')}
/>
handleDragEnd(e, 'yellow')}
/>
handleDragEnd(e, 'red')}
/>
);
};
export default App;
````
```js
```
---
# HTML5 Canvas Konva 停止动画教程
> 学习如何使用 Konva.Animation 的 start() 和 stop() 方法,启动和停止 Konva 中的 Canvas 动画。
Source: https://konvajs.org/zh-Hans/docs/animations/Stop_Animation.html
如需使用 Konva 停止动画,可以使用 `stop()` 方法。
如需重新启动动画,可以再次调用 `start()`。
**操作说明:** 单击“Start”启动动画,单击“Stop”停止动画。
如需查看完整的属性和方法列表,请参阅 [Konva.Animation 文档](/zh-Hans/api/Konva.Animation.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
layer.add(circle);
// add buttons
const container = document.createElement('div');
document.body.appendChild(container);
container.style.position = 'absolute';
container.style.top = '0px';
container.style.left = '0px';
const startBtn = document.createElement('button');
startBtn.textContent = 'Start Animation';
container.appendChild(startBtn);
const stopBtn = document.createElement('button');
stopBtn.textContent = 'Stop Animation';
container.appendChild(stopBtn);
const anim = new Konva.Animation(function(frame) {
circle.x(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
stage.width() / 2
);
}, layer);
const amplitude = 100;
const period = 2000;
startBtn.addEventListener('click', () => anim.start());
stopBtn.addEventListener('click', () => anim.stop());
````
```js
import { Stage, Layer, Circle } from 'react-konva';
import { useEffect, useRef, useState } from 'react';
const App = () => {
const circleRef = useRef(null);
const [isAnimating, setIsAnimating] = useState(false);
const animRef = useRef(null);
useEffect(() => {
const amplitude = 100;
const period = 2000;
animRef.current = new Konva.Animation((frame) => {
circleRef.current.x(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
window.innerWidth / 2
);
}, circleRef.current.getLayer());
return () => {
if (animRef.current) {
animRef.current.stop();
}
};
}, []);
const handleStart = () => {
animRef.current.start();
setIsAnimating(true);
};
const handleStop = () => {
animRef.current.stop();
setIsAnimating(false);
};
return (
);
};
export default App;
````
```js
```
---
# 文本动画教程
> 学习如何在 Konva 中使用 charRenderFunc 属性创建逐字符文本动画,为每个字符应用渲染效果。
Source: https://konvajs.org/zh-Hans/docs/animations/Text_Animations.html
**注意:此功能仅在 Konva v10.0.0 及更高版本中可用。**
Konva 通过 `charRenderFunc` 属性提供文本动画功能。此函数让你可以自定义每个字符的渲染方式,从而创建逐字符动画和效果。
```js
var text = new Konva.Text({
x: 10,
y: 10,
text: 'AB',
fontSize: 20,
charRenderFunc: function ({ context, index }) {
if (index === 1) {
// shift only the second character
context.translate(0, 10);
}
},
});
```
`charRenderFunc` 接收一个上下文对象,其中包含以下参数:
- **`char`** - 实际渲染的字符字符串
- **`index`** - 字符在完整文本中的从零开始的索引
- **`x`** - 渲染字符的 X 坐标
- **`y`** - 渲染字符的 Y 坐标
- **`lineIndex`** - 包含此字符的行的从零开始的索引
- **`column`** - 当前行中从零开始的列位置
- **`isLastInLine`** - 指示此字符是否为所在行最后一个字符的布尔值
- **`width`** - 字符宽度
- **`context`** - Canvas 2D 渲染上下文,用于应用变换、不透明度和颜色等效果
你可以根据各个字符的位置和属性,为它们应用变换、不透明度变化或其他效果。
如果你的效果会将字符移出文本框,请使用 `selfRectFunc`(需要 Konva 10.7.0 或更高版本)描述字符可能到达的区域。这样,缓存、[隔离组](/zh-Hans/docs/groups_and_layers/Isolated_Groups.html)和 Transformer 都会包含这些字符:
```javascript
const text = new Konva.Text({
text: 'Bouncing text',
// characters jump up to 20px above the text box
selfRectFunc: (shape) => ({
x: 0,
y: -20,
width: shape.width(),
height: shape.height() + 20,
}),
charRenderFunc: ({ context, index }) => {
context.translate(0, -Math.abs(Math.sin(index)) * 20);
},
});
```
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// we will store the opacity of each character in an array
const charOpacities = [];
const textNode = new Konva.Text({
x: window.innerWidth / 2 - 100,
y: window.innerHeight / 2 - 20,
text: 'ANIMATION',
fontSize: 40,
fontFamily: 'Arial',
fill: '#333',
charRenderFunc: function ({ context, index }) {
context.globalAlpha = charOpacities[index];
},
});
layer.add(textNode);
const anim = new Konva.Animation(function(frame) {
const time = frame.time;
const cycleDuration = 4000; // 4 seconds total cycle
const fadeInDuration = 1500; // 1.5 seconds to fade in all
const holdDuration = 1000; // 1 second hold
const fadeOutDuration = 1500; // 1.5 seconds to fade out all
const cycleTime = time % cycleDuration;
for (let i = 0; i < textNode.text().length; i++) {
const charDelay = i * 150; // 150ms delay between characters
if (cycleTime < fadeInDuration) {
// Fade in phase
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacities[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// Hold phase - all characters visible
charOpacities[i] = 1;
} else {
// Fade out phase
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // Same order as fade in
const charFadeOutTime = Math.max(0, cycleTime - fadeOutStart - charFadeOutDelay);
charOpacities[i] = Math.max(0, 1 - charFadeOutTime / 300);
}
}
}, layer);
anim.start();
```
```js
import { Stage, Layer, Text } from 'react-konva';
import { useEffect, useRef } from 'react';
const App = () => {
const textRef = useRef(null);
const layerRef = useRef(null);
const charOpacitiesRef = useRef([]);
useEffect(() => {
const anim = new Konva.Animation((frame) => {
const time = frame.time;
const cycleDuration = 4000; // 4 seconds total cycle
const fadeInDuration = 1500; // 1.5 seconds to fade in all
const holdDuration = 1000; // 1 second hold
const fadeOutDuration = 1500; // 1.5 seconds to fade out all
const cycleTime = time % cycleDuration;
for (let i = 0; i < textRef.current.text().length; i++) {
const charDelay = i * 150; // 150ms delay between characters
if (cycleTime < fadeInDuration) {
// Fade in phase
const charStartTime = charDelay;
const charFadeTime = Math.max(0, cycleTime - charStartTime);
charOpacitiesRef.current[i] = Math.min(1, charFadeTime / 300);
} else if (cycleTime < fadeInDuration + holdDuration) {
// Hold phase - all characters visible
charOpacitiesRef.current[i] = 1;
} else {
// Fade out phase
const fadeOutStart = fadeInDuration + holdDuration;
const charFadeOutDelay = i * 150; // Same order as fade in
const charFadeOutTime = Math.max(0, cycleTime - fadeOutStart - charFadeOutDelay);
charOpacitiesRef.current[i] = Math.max(0, 1 - charFadeOutTime / 300);
}
}
}, layerRef.current);
anim.start();
return () => {
anim.stop();
};
}, []);
return (
{
context.globalAlpha = charOpacitiesRef.current[index] || 0;
}}
/>
);
};
export default App;
```
```js
```
---
# 裁剪函数教程
> 学习如何在 Konva 中使用 clipFunc 自定义裁剪函数,为组和图层创建复杂的裁剪区域。
Source: https://konvajs.org/zh-Hans/docs/clipping/Clipping_Function.html
## 如何裁剪图层中的节点?
要使用 Konva 在复杂的裁剪区域内绘制内容,可以设置组或图层的 `clipFunc`
属性。
在本教程中,我们将在应用到组的双圆裁剪区域内绘制多个圆形。
```js
import Konva from 'konva';
// First we need to create stage
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
// Then create layer
const layer = new Konva.Layer();
const group = new Konva.Group({
clipFunc: function (ctx) {
ctx.beginPath();
ctx.arc(200, 120, 50, 0, Math.PI * 2, false);
ctx.arc(280, 120, 50, 0, Math.PI * 2, false);
},
});
for (let i = 0; i < 20; i++) {
const blob = new Konva.Circle({
x: Math.random() * stage.width(),
y: Math.random() * stage.height(),
radius: Math.random() * 50,
fill: 'green',
opacity: 0.8,
});
group.add(blob);
}
// add the shape to the layer
layer.add(group);
// add the layer to the stage
stage.add(layer);
```
```js
import { Stage, Layer, Group, Circle } from 'react-konva';
const App = () => {
const blobs = Array.from({ length: 20 }, (_, i) => ({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: Math.random() * 50,
}));
return (
{
ctx.beginPath();
ctx.arc(200, 120, 50, 0, Math.PI * 2, false);
ctx.arc(280, 120, 50, 0, Math.PI * 2, false);
}}
>
{blobs.map((blob, i) => (
))}
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 简单裁剪教程
> 学习如何在 Konva 中使用 clip 属性,为组和图层应用简单的矩形裁剪区域。
Source: https://konvajs.org/zh-Hans/docs/clipping/Clipping_Regions.html
要使用 Konva 在裁剪区域内绘制内容,可以设置组或图层的 `clip`
属性。
裁剪区域由 `x`、`y`、`width` 和 `height` 定义。在本教程中,
我们将在应用到组的矩形裁剪区域内绘制多个圆形。
对于更复杂的情况,请参阅裁剪函数。[裁剪函数](/zh-Hans/docs/clipping/Clipping_Function.html)
```js
import Konva from 'konva';
// First we need to create stage
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
// Then create layer
const layer = new Konva.Layer();
const group = new Konva.Group({
clip: {
x: 100,
y: 20,
width: 200,
height: 200,
},
});
for (let i = 0; i < 20; i++) {
const blob = new Konva.Circle({
x: Math.random() * stage.width(),
y: Math.random() * stage.height(),
radius: Math.random() * 50,
fill: 'green',
opacity: 0.8,
});
group.add(blob);
}
// add the shape to the layer
layer.add(group);
// add the layer to the stage
stage.add(layer);
```
```js
import { Stage, Layer, Group, Circle } from 'react-konva';
const App = () => {
const blobs = Array.from({ length: 20 }, (_, i) => ({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
radius: Math.random() * 50,
}));
return (
{blobs.map((blob, i) => (
))}
);
};
export default App;
```
```js
```
---
# 保存和加载 HTML5 Canvas 舞台的最佳实践
> 使用 Konva.js 保存和加载 HTML5 Canvas 状态的最佳实践,包括序列化、数据管理和状态持久化技巧。
Source: https://konvajs.org/zh-Hans/docs/data_and_serialization/Best_Practices.html
## 保存和加载完整舞台内容并实现撤销和重做的最佳方式是什么?
如果要保存或加载简单的 Canvas 内容,可以使用内置的 `Konva` 方法:`node.toJSON()` 和 `Node.create(json)`。
请参阅[简单加载](/zh-Hans/docs/data_and_serialization/Simple_Load.html)和[复杂加载](/zh-Hans/docs/data_and_serialization/Complex_Load.html)示例。
但这些方法只适用于很小的应用。在较大的应用中,这些方法很难使用。因为大型应用的树结构通常很复杂,其中可能包含大量事件监听器、图像和滤镜等内容。这些数据无法序列化为 JSON,或很难完成序列化。
树中的节点通常还包含大量与应用状态没有直接关系的信息。这些信息仅用于描述应用的视觉外观。
例如,假设一个游戏在 Canvas 中绘制了多个球。每个球不仅是一个圆形,还是一个复杂的视觉对象组,其中包含阴影和文本(例如“中国制造”)。现在,假设你要序列化应用状态并在其他位置使用,例如发送到另一台计算机或实现撤销和重做。几乎所有视觉信息(阴影、文本和尺寸)都不是关键信息,可能不需要保存,因为所有球都使用相同的阴影和尺寸等设置。真正关键的信息只有球的数量及其坐标。你只需要保存和加载这些信息。它们只是一个简单的数组:
```javascript
var state = [{x: 10, y: 10}, { x: 160, y: 1041}]
```
获得这些信息后,你需要一个可以创建完整 Canvas 结构的函数。
如果要更新 Canvas,例如创建一个新球,不需要直接创建新的 Canvas 节点(例如创建 `Konva.Circle` 的新实例)。只需向状态中添加一个新对象,然后更新或重新创建 Canvas。
这样,在保存和加载阶段就不需要处理图像加载、滤镜和事件监听器等内容。因为这些操作都在 `create` 或 `update` 函数中完成。
如果你了解 `React`、`Vue` 和 `Angular` 等现代框架的工作方式,就能更好地理解这种方法。
还可以查看以下示例以进一步了解这种方法:
1. [使用 React 实现撤销和重做](/zh-Hans/docs/react/Undo-Redo.html)
1. [使用 Vue 实现保存和加载](/zh-Hans/docs/vue/Save-Load.html)
如何实现 `create` 和 `update` 函数取决于具体情况。使用 [react-konva](/zh-Hans/docs/react/index.html) 等能处理这些工作的框架会更容易。
如果不想使用此类框架,需要根据自己的应用来设计。下面的小型示例介绍一种实现方式。
最简单的方法是只实现一个 `create(state)` 函数,由它完成所有复杂的加载工作。
应用发生更改时,只需销毁 Canvas 并创建一个新 Canvas。但这种方法可能导致性能下降。
更合理的实现是创建 `create(state)` 和 `update(state)` 两个函数。`create` 创建所有必需对象的实例、绑定事件并加载图像。`update` 更新节点的属性。如果对象数量发生变化,则销毁所有对象并从头创建。如果只有部分属性发生变化,则调用 `update`。
**操作说明:**此示例包含多个带滤镜的图像。你可以添加和移动图像,单击图像以应用新滤镜,并使用撤销和重做功能。
```js
import Konva from 'konva';
// Initial state
let state = {
images: [
{ x: 50, y: 50, filter: 'none' },
{ x: 150, y: 50, filter: 'blur' }
]
};
// History for undo/redo
const history = [JSON.stringify(state)];
let historyStep = 0;
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// Create container
const container = document.createElement('div');
container.style.position = 'relative';
document.body.appendChild(container);
// Create button container
const buttonContainer = document.createElement('div');
buttonContainer.style.position = 'absolute';
buttonContainer.style.top = '10px';
buttonContainer.style.left = '10px';
buttonContainer.style.zIndex = '10';
container.appendChild(buttonContainer);
// Create UI buttons
const addButton = document.createElement('button');
addButton.textContent = 'Add Image';
addButton.style.margin = '0 5px';
buttonContainer.appendChild(addButton);
const undoButton = document.createElement('button');
undoButton.textContent = 'Undo';
undoButton.style.margin = '0 5px';
buttonContainer.appendChild(undoButton);
const redoButton = document.createElement('button');
redoButton.textContent = 'Redo';
redoButton.style.margin = '0 5px';
buttonContainer.appendChild(redoButton);
// Move stage container into our container
const stageContainer = document.getElementById('container');
container.appendChild(stageContainer);
stageContainer.style.position = 'absolute';
stageContainer.style.top = '0';
stageContainer.style.left = '0';
// Load image
const imageObj = new Image();
imageObj.src = 'https://konvajs.org/assets/lion.png';
function createImage(imageConfig) {
const image = new Konva.Image({
image: imageObj,
x: imageConfig.x,
y: imageConfig.y,
width: 100,
height: 100,
draggable: true
});
if (imageConfig.filter === 'blur') {
image.cache();
image.filters([Konva.Filters.Blur]);
image.blurRadius(10);
}
return image;
}
function create(state) {
layer.destroyChildren();
state.images.forEach(imgConfig => {
const image = createImage(imgConfig);
image.on('dragend', () => {
const pos = image.position();
const index = layer.children.indexOf(image);
state.images[index] = {
...state.images[index],
x: pos.x,
y: pos.y
};
saveHistory();
});
image.on('click', () => {
const index = layer.children.indexOf(image);
state.images[index] = {
...state.images[index],
filter: state.images[index].filter === 'none' ? 'blur' : 'none'
};
saveHistory();
create(state);
});
layer.add(image);
});
}
function saveHistory() {
historyStep++;
history.length = historyStep;
history.push(JSON.stringify(state));
}
// Add event listeners
addButton.addEventListener('click', () => {
state.images.push({
x: Math.random() * stage.width(),
y: Math.random() * stage.height(),
filter: 'none'
});
saveHistory();
create(state);
});
undoButton.addEventListener('click', () => {
if (historyStep === 0) return;
historyStep--;
state = JSON.parse(history[historyStep]);
create(state);
});
redoButton.addEventListener('click', () => {
if (historyStep === history.length - 1) return;
historyStep++;
state = JSON.parse(history[historyStep]);
create(state);
});
imageObj.onload = () => {
create(state);
};
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const FilteredImage = ({ filter, ...props }) => {
const imageRef = useRef();
useEffect(() => {
imageRef.current?.cache();
}, [props.image]);
return (
);
};
const App = () => {
const [images, setImages] = useState([
{ x: 50, y: 50, filter: 'none' },
{ x: 150, y: 50, filter: 'blur' }
]);
const [history, setHistory] = useState([]);
const [historyStep, setHistoryStep] = useState(0);
const [lionImage] = useImage('https://konvajs.org/assets/lion.png', 'anonymous');
useEffect(() => {
if (lionImage) {
setHistory([JSON.stringify(images)]);
}
}, [lionImage]);
const handleDragEnd = (index, e) => {
const newImages = [...images];
newImages[index] = {
...newImages[index],
x: e.target.x(),
y: e.target.y()
};
setImages(newImages);
saveHistory(newImages);
};
const handleClick = (index) => {
const newImages = [...images];
newImages[index] = {
...newImages[index],
filter: newImages[index].filter === 'none' ? 'blur' : 'none'
};
setImages(newImages);
saveHistory(newImages);
};
const saveHistory = (newImages) => {
const newHistory = history.slice(0, historyStep + 1);
newHistory.push(JSON.stringify(newImages));
setHistory(newHistory);
setHistoryStep(newHistory.length - 1);
};
const handleAdd = () => {
const newImages = [...images, {
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
filter: 'none'
}];
setImages(newImages);
saveHistory(newImages);
};
const handleUndo = () => {
if (historyStep === 0) return;
const newStep = historyStep - 1;
setHistoryStep(newStep);
setImages(JSON.parse(history[newStep]));
};
const handleRedo = () => {
if (historyStep === history.length - 1) return;
const newStep = historyStep + 1;
setHistoryStep(newStep);
setImages(JSON.parse(history[newStep]));
};
return (
{lionImage && images.map((img, i) => (
handleDragEnd(i, e)}
onClick={() => handleClick(i)}
/>
))}
);
};
export default App;
```
```js
```
---
# 从 JSON 加载 HTML5 Canvas 舞台教程
> 使用 Konva.Node.create 和 find 选择器,从 JSON 加载包含图像和事件绑定的 Konva 舞台。
Source: https://konvajs.org/zh-Hans/docs/data_and_serialization/Complex_Load.html
要使用 Konva 加载原来包含图像和事件绑定的复杂舞台,
需要使用 `Konva.Node.create()` 创建舞台节点,然后配合选择器使用 `find()` 方法
设置图像和事件处理程序。
图像和事件处理程序无法序列化,因此必须手动设置。
**这些方法适用于小型应用。对于更复杂的情况,请参阅[最佳实践](/zh-Hans/docs/data_and_serialization/Best_Practices.html)。**
```js
import Konva from 'konva';
// JSON string from a previous save
const json = '{"attrs":{"width":578,"height":200},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":100,"y":100,"sides":6,"radius":70,"fill":"red","stroke":"black","strokeWidth":4},"className":"RegularPolygon"}]}]}';
// create node using json string
const stage = Konva.Node.create(json, 'container');
// get reference to the hexagon
const hexagon = stage.findOne('RegularPolygon');
// bind events
hexagon.on('click', () => {
hexagon.fill(Konva.Util.getRandomColor());
});
```
**注意:**在 React 中直接使用 `Konva.Node.create()` 是一种反模式。在 React 应用中,应分别管理状态和视图。不要反序列化完整的节点结构,而应加载定义图形的数据,并让 React 组件处理渲染。以下示例演示如何在 React 中将图形数据加载为 state:
```js
import { Stage, Layer, RegularPolygon } from 'react-konva';
import { useState, useEffect } from 'react';
import Konva from 'konva';
const App = () => {
const [shapeData, setShapeData] = useState(null);
useEffect(() => {
// Simulating loading JSON data from storage or API
const loadData = () => {
// This would typically come from localStorage, API, etc.
const jsonString = '{"hexagon":{"x":100,"y":100,"sides":6,"radius":70,"fill":"red","stroke":"black","strokeWidth":4}}';
try {
// Parse the JSON into a JavaScript object
const data = JSON.parse(jsonString);
setShapeData(data);
} catch (error) {
console.error('Error parsing JSON:', error);
}
};
loadData();
}, []);
const handleClick = () => {
if (shapeData) {
setShapeData({
...shapeData,
hexagon: {
...shapeData.hexagon,
fill: Konva.Util.getRandomColor()
}
});
}
};
// Don't render until we have data
if (!shapeData) return Loading...
;
return (
);
};
export default App;
```
**注意:**在 Vue 中直接使用 `Konva.Node.create()` 是一种反模式。在 Vue 应用中,应使用响应式数据管理状态,并将状态与视图分开。不要反序列化完整的节点结构,而应加载定义图形的数据,并让 Vue 组件处理渲染。以下示例演示如何在 Vue 中将图形数据加载为响应式状态:
```js
```
---
# HTML5 Canvas 导出高质量图像教程
> 学习如何使用 Konva.js 将 HTML5 Canvas 导出为高质量 PNG 或 JPEG 图像,以及如何配合 pixelRatio 使用 stage.toDataURL() 实现 Retina 质量的导出。
Source: https://konvajs.org/zh-Hans/docs/data_and_serialization/High-Quality-Export.html
如果需要将舞台导出为图像或 base64,可以使用 `stage.toDataURL()` 或 `stage.toImage()` 方法。
默认情况下,`Konva` 将导出图像的 `pixelRatio` 属性设置为 `1`。这意味着,如果导出尺寸为 `500x500` 的舞台,导出图像的尺寸也为 `500x500`。
在某些情况下,你可能要导出更适合较高或较低分辨率的图像。例如,你可能要导出一个图像,然后在 HDPI 设备的 Canvas 上使用它。这类设备具有较高的像素比,例如 Retina 显示屏。另一种情况是,你需要为使用高分辨率显示器的计算机导出用户的绘图。
如果使用默认设置执行此操作,图像会变得模糊。有关全局 `pixelRatio` 属性的更多信息,请参阅 [MDN - devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio)。
对于这两种情况,可以使用:
```javascript
stage.toDataURL({
pixelRatio: 2 // or other value you need
})
```
现在,尺寸为 `500x500` 的舞台会导出为尺寸为 `1000x1000` 的图像。除了位图图像和缓存节点外,`Konva` 中几乎所有节点都存储为矢量数据。因此可以导出高质量图像。
**操作说明:**尝试将舞台保存为图像。你会看到该图像具有较高分辨率。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: 400,
height: 400
});
const layer = new Konva.Layer();
stage.add(layer);
// create some shapes
const circle = new Konva.Circle({
x: 200,
y: 200,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});
const text = new Konva.Text({
x: 150,
y: 190,
text: 'High Quality Export',
fontSize: 20,
fill: 'white'
});
layer.add(circle);
layer.add(text);
// add button
const button = document.createElement('button');
button.textContent = 'Save as High Quality Image';
document.body.appendChild(button);
button.addEventListener('click', () => {
// save stage as a high quality image
const dataURL = stage.toDataURL({
pixelRatio: 2 // double resolution
});
// create link to download
const link = document.createElement('a');
link.download = 'stage.png';
link.href = dataURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
```
```js
import { Stage, Layer, Circle, Text } from 'react-konva';
import { useRef } from 'react';
const App = () => {
const stageRef = useRef(null);
const handleExport = () => {
const dataURL = stageRef.current.toDataURL({
pixelRatio: 2 // double resolution
});
const link = document.createElement('a');
link.download = 'stage.png';
link.href = dataURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return (
);
};
export default App;
```
```js
```
## 浏览器尺寸限制
每个浏览器都会限制 Canvas 的宽度、高度和总面积,移动端的限制更低。
超过限制后,较大的 `pixelRatio` 会静默失败:调用返回空白或截断的图像,
而不会引发错误。大幅面输出必须分块处理,或在浏览器外渲染。
---
# 从 JSON 加载简单 HTML5 Canvas 舞台教程
> 学习如何使用 Konva.js 从 JSON 加载 HTML5 Canvas 舞台,以及如何使用 Konva.Node.create() 恢复已保存的 Canvas 状态。
Source: https://konvajs.org/zh-Hans/docs/data_and_serialization/Simple_Load.html
要使用 Konva 从 JSON 加载简单舞台,可以使用 `Konva.Node.create()` 方法。
`create()` 方法接受 JSON 字符串和容器 id 作为参数。
```js
import Konva from 'konva';
// JSON string from a previous save
const json = '{"attrs":{"width":400,"height":400},"className":"Stage","children":[{"attrs":{},"className":"Layer","children":[{"attrs":{"x":100,"y":100,"radius":50,"fill":"red","stroke":"black","strokeWidth":3},"className":"Circle"}]}]}';
// create node using json string
const stage = Konva.Node.create(json, 'container');
// you can keep adding events, etc
const circle = stage.findOne('Circle');
circle.on('click', () => {
circle.fill(Konva.Util.getRandomColor());
});
```
**注意:**在 React 或 Vue 中直接使用 `Konva.Node.create()` 是一种反模式。在这些框架中,应分别管理状态(数据)和视图(组件)。不要序列化和加载完整的节点结构,而应保存和加载定义图形的数据,然后让框架组件处理渲染。这种方法更符合 React 和 Vue 的声明式、状态驱动模式,也能更好地控制组件生命周期和事件。
```js
import { Stage, Layer, Circle } from 'react-konva';
import { useState, useEffect } from 'react';
import Konva from 'konva';
const App = () => {
// In React, we store shape data as state instead of using Konva.Node.create()
const [shapeData, setShapeData] = useState(null);
useEffect(() => {
// Simulating loading JSON data from storage or API
const savedShapeData = {
circle: {
x: 100,
y: 100,
radius: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 3
},
// We could have more shapes here
};
// In a real app, this might be:
// fetch('/api/shapes').then(response => response.json()).then(setShapeData)
setShapeData(savedShapeData);
}, []);
const handleCircleClick = () => {
setShapeData({
...shapeData,
circle: {
...shapeData.circle,
fill: Konva.Util.getRandomColor()
}
});
};
// Don't render until we have data
if (!shapeData) return Loading...
;
return (
);
};
export default App;
```
**注意:**在 React 或 Vue 中直接使用 `Konva.Node.create()` 是一种反模式。在这些框架中,应分别管理状态(数据)和视图(组件)。不要序列化和加载完整的节点结构,而应保存和加载定义图形的数据,然后让框架组件处理渲染。这种方法更符合 React 和 Vue 的声明式、状态驱动模式,也能更好地控制组件生命周期和事件。
```js
Loading...
```
---
# Canvas 截图——使用 JavaScript 将 HTML5 Canvas 导出为图像
> 使用 JavaScript 截取 HTML5 Canvas 并导出为 PNG 或 JPEG。使用 Konva.js 的 toDataURL() 将 Canvas 内容捕获为 base64 图像或可下载文件。
Source: https://konvajs.org/zh-Hans/docs/data_and_serialization/Stage_Data_URL.html
要使用 `Konva` 截取 Canvas 并将其导出为图像,请使用 `toDataURL()`
方法。此方法对所有节点类型(包括 `Stage`)都直接返回数据 URL。
你可以传入 image/jpeg 等 MIME 类型和 0 到 1 之间的质量值。
还可以截取特定节点,包括图层、组和图形。
*注意:`toDataURL()` 方法要求 Canvas 上绘制的所有图像
与执行代码托管在同一域名下的 Web 服务器上。
如果不满足此条件,将引发 SECURITY_ERR 异常。*
**操作说明:**拖放矩形,然后单击保存按钮以获取合成数据 URL,并在新窗口中打开生成的图像。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: 400,
height: 400
});
const layer = new Konva.Layer();
stage.add(layer);
// create draggable rectangle
const rect = new Konva.Rect({
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
layer.add(rect);
// add button
const button = document.createElement('button');
button.textContent = 'Save as Image';
document.body.appendChild(button);
button.addEventListener('click', () => {
// get data URL with default settings
const dataURL = stage.toDataURL();
// open in new window
const win = window.open();
win.document.write(`
`);
// you can also save with different settings
const jpegURL = stage.toDataURL({
mimeType: 'image/jpeg',
quality: 0.8
});
console.log('JPEG URL:', jpegURL);
});
```
```js
import { Stage, Layer, Rect } from 'react-konva';
import { useRef, useState } from 'react';
const App = () => {
const stageRef = useRef(null);
const [position, setPosition] = useState({ x: 100, y: 100 });
const handleExport = () => {
// get data URL with default settings
const dataURL = stageRef.current.toDataURL();
// open in new window
const win = window.open();
win.document.write(`
`);
// you can also save with different settings
const jpegURL = stageRef.current.toDataURL({
mimeType: 'image/jpeg',
quality: 0.8
});
console.log('JPEG URL:', jpegURL);
};
const handleDragEnd = (e) => {
setPosition({
x: e.target.x(),
y: e.target.y()
});
};
return (
);
};
export default App;
```
```js
```
---
# 如何支持 Konva 项目并捐款?
> 通过 Patreon、Open Collective 或 GitHub Sponsors 支持 Konva 开发,帮助维护该框架。
Source: https://konvajs.org/zh-Hans/docs/donate.html
你好,我叫 Anton,是 `Konva` 框架的核心维护者。
**如果你想支持 `Konva` 及其所有生态系统工具(例如 `react-konva` 和 `vue-konva`)的开发,可以使用以下渠道:**
- [Patreon](https://www.patreon.com/lavrton)
- [Open Collective](https://opencollective.com/konva)
- [GitHub 赞助](https://github.com/sponsors/lavrton)
我投入大量时间为 `Konva` 用户提供支持,并开发包含错误修复和新功能的新版本。
如果你通过在项目中使用 `Konva` 获得收入,支持 `Konva` 的开发是合理的。你的支持有助于保持框架的质量和持续维护。
即使项目没有带来收入,如果 `Konva` 为你节省了大量时间,也欢迎你提供支持。
### 你的公司在使用 Konva 吗?
许多开发者可能难以提供资金捐助。但是,如果你在公司工作中使用 `konva`,可以与管理人员沟通,让公司支持该项目。得到充分支持的项目可以为公司带来价值。
---
# HTML5 Canvas 复杂拖放边界
> 学习如何使用 dragmove 事件,将 Konva 中的拖放操作限制在自定义边界和区域内。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Complex_Drag_and_Drop.html
如需使用 Konva 将正在拖放的节点限制在指定区域内,
可以使用 `dragmove` 事件定义节点无法越过的边界。
_提示:你可以使用 `shape.absolutePosition()` 方法获取或设置节点的绝对位置,而不是使用相对的 `x` 和 `y`。_
**操作说明:** 拖放浅蓝色矩形,并观察它
被限制在 y = 50 的假想边界下方。拖放黄色
矩形,并观察它被限制在假想圆形内。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const blueGroup = new Konva.Group({
x: 30,
y: 70,
draggable: true,
});
// bound below y=50
blueGroup.on('dragmove', () => {
blueGroup.y(Math.max(blueGroup.y(), 50));
});
// bound inside a circle
const yellowGroup = new Konva.Group({
x: stage.width() / 2,
y: 70,
draggable: true,
});
yellowGroup.on('dragmove', () => {
const x = stage.width() / 2;
const y = 70;
const radius = 50;
const pos = yellowGroup.absolutePosition();
const scale = radius / Math.sqrt(Math.pow(pos.x - x, 2) + Math.pow(pos.y - y, 2));
if (scale < 1) {
yellowGroup.x(Math.round((pos.x - x) * scale + x));
yellowGroup.y(Math.round((pos.y - y) * scale + y));
}
});
const blueText = new Konva.Text({
fontSize: 26,
fontFamily: 'Calibri',
text: 'bound below',
fill: 'black',
padding: 10,
width: 150,
align: 'center',
});
const blueRect = new Konva.Rect({
width: 150,
height: 72,
fill: '#aaf',
stroke: 'black',
strokeWidth: 4,
});
const yellowText = new Konva.Text({
fontSize: 26,
fontFamily: 'Calibri',
text: 'bound in circle',
fill: 'black',
padding: 10,
width: 150,
align: 'center',
});
const yellowRect = new Konva.Rect({
width: 150,
height:72,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
});
blueGroup.add(blueRect).add(blueText);
yellowGroup.add(yellowRect).add(yellowText);
layer.add(blueGroup);
layer.add(yellowGroup);
stage.add(layer);
````
```jsx
import { Stage, Layer, Group, Rect, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [bluePosition, setBluePosition] = useState({ x: 30, y: 70 });
const [yellowPosition, setYellowPosition] = useState({
x: window.innerWidth / 2,
y: 70,
});
const handleBlueDragMove = (e) => {
// At the boundary, y stays 50 in state. React-Konva skips unchanged props.
// Constrain the node directly before saving its position in state.
e.target.y(Math.max(e.target.y(), 50));
setBluePosition(e.target.position());
};
const handleYellowDragMove = (e) => {
const x = window.innerWidth / 2;
const y = 70;
const radius = 50;
const position = e.target.absolutePosition();
const scale =
radius /
Math.sqrt(
Math.pow(position.x - x, 2) + Math.pow(position.y - y, 2)
);
if (scale < 1) {
// Repeated drags can produce the same boundary coordinates.
// Set the node directly because React-Konva skips unchanged props.
e.target.absolutePosition({
x: Math.round((position.x - x) * scale + x),
y: Math.round((position.y - y) * scale + y),
});
}
setYellowPosition(e.target.position());
};
return (
);
};
export default App;
````
```vue
```
---
# HTML5 Canvas 拖放事件
> 学习如何使用 Konva.js 处理 HTML5 Canvas 上的拖放事件。使用 dragstart、dragmove 和 dragend 事件响应图形拖动。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drag_Events.html
如需检测 Konva 拖放事件,可以使用 `on()` 方法将
`dragstart`、`dragmove` 或 `dragend` 事件绑定到节点。
`on()` 方法需要事件类型和事件发生时要运行的函数。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 40,
y: 40,
text: 'Draggable Text',
fontSize: 20,
draggable: true,
width: 200,
});
layer.add(text);
const status = new Konva.Text({
x: 40,
y: 100,
text: '',
fontSize: 16,
width: 200,
});
layer.add(status);
text.on('dragstart', () => {
status.text('drag started');
});
text.on('dragend', () => {
status.text('drag ended');
});
text.on('dragmove', () => {
status.text('dragging');
});
```
```jsx
import { Stage, Layer, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [position, setPosition] = useState({ x: 40, y: 40 });
const [status, setStatus] = useState('');
const handleDrag = (e, nextStatus) => {
setPosition({ x: e.target.x(), y: e.target.y() });
setStatus(nextStatus);
};
return (
setStatus('drag started')}
onDragEnd={(e) => handleDrag(e, 'drag ended')}
onDragMove={(e) => handleDrag(e, 'dragging')}
/>
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas 拖放组教程
> 学习如何使用 Konva 的 draggable 属性,在 HTML5 Canvas 上一起拖放一组图形。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drag_a_Group.html
如需使用 Konva 拖放组,可以在创建组时将配置对象的 `draggable` 属性
设置为 `true`,也可以使用 `draggable()` 方法。
注意:拖动组不会更改任何子节点的 `x` 和 `y` 属性。更改的是组本身的属性。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const group = new Konva.Group({
draggable: true,
});
layer.add(group);
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'];
for (let i = 0; i < 6; i++) {
const box = new Konva.Rect({
x: i * 30 + 10,
y: i * 18 + 40,
width: 100,
height: 50,
name: colors[i],
fill: colors[i],
stroke: 'black',
strokeWidth: 4,
});
group.add(box);
}
group.on('mouseover', function () {
document.body.style.cursor = 'move';
});
group.on('mouseout', function () {
document.body.style.cursor = 'default';
});
```
```jsx
import { Stage, Layer, Group, Rect, Text } from 'react-konva';
const App = () => {
const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'];
const handleMouseOver = () => {
document.body.style.cursor = 'move';
};
const handleMouseOut = () => {
document.body.style.cursor = 'default';
};
return (
{colors.map((color, i) => (
))}
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas 拖放线条
> 学习如何通过将 draggable 属性设置为 true,使用 Konva 在 HTML5 Canvas 上拖放线条。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drag_a_Line.html
如需使用 Konva 拖放线条,可以在创建线条时将配置对象的 `draggable` 属性
设置为 `true`,也可以使用 `draggable()` 方法。
**注意:拖动线条不会更改 `points` 属性,而会更改线条的 `x` 和 `y` 属性。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const redLine = new Konva.Line({
x: 50,
y: 50,
points: [0, 0, 150, 0],
stroke: 'red',
strokeWidth: 15,
lineCap: 'round',
lineJoin: 'round',
draggable: true,
});
// add cursor styling
redLine.on('mouseover', function () {
document.body.style.cursor = 'pointer';
});
redLine.on('mouseout', function () {
document.body.style.cursor = 'default';
});
layer.add(redLine);
stage.add(layer);
```
```jsx
import { Stage, Layer, Line } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
return (
{
setPosition({
x: e.target.x(),
y: e.target.y(),
});
}}
onMouseEnter={(e) => {
document.body.style.cursor = 'pointer';
}}
onMouseLeave={(e) => {
document.body.style.cursor = 'default';
}}
/>
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas 拖放舞台
> 学习如何使整个 Konva 舞台可以拖动,让用户通过拖动任意区域来平移 Canvas。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drag_a_Stage.html
如需使用 Konva 拖放舞台,可以在创建组时将配置对象的 `draggable` 属性
设置为 `true`,也可以使用 `draggable()` 方法。
与图形、组和图层等其他节点的拖放不同,
你可以拖动舞台的任意部分来拖动整个舞台。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
draggable: true
});
const layer = new Konva.Layer();
stage.add(layer);
// create circle
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
});
// create text
const text = new Konva.Text({
x: 10,
y: 10,
text: 'Drag the stage anywhere',
fontSize: 20,
fontFamily: 'Calibri',
fill: 'black'
});
layer.add(circle);
layer.add(text);
```
```jsx
import { Stage, Layer, Circle, Text } from 'react-konva';
const App = () => {
return (
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas 拖放图像
> 学习如何通过将 draggable 属性设置为 true,使用 Konva 在 HTML5 Canvas 上拖放图像。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drag_an_Image.html
如需使用 Konva 拖放图像,可以在创建图形时将 `draggable` 属性
设置为 true,也可以使用 `draggable()` 方法。
`draggable()` 方法会自动为桌面应用和移动应用启用拖放功能。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const imageObj = new Image();
imageObj.onload = () => {
const yoda = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
width: 106,
height: 118,
draggable: true,
});
// add cursor styling
yoda.on('mouseover', function () {
document.body.style.cursor = 'pointer';
});
yoda.on('mouseout', function () {
document.body.style.cursor = 'default';
});
layer.add(yoda);
};
imageObj.src = 'https://konvajs.org/assets/yoda.jpg';
stage.add(layer);
```
```jsx
import { Stage, Layer, Image } from 'react-konva';
import { useState } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [yodaImage] = useImage('https://konvajs.org/assets/yoda.jpg');
return (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
onMouseEnter={(e) => {
document.body.style.cursor = 'pointer';
}}
onMouseLeave={(e) => {
document.body.style.cursor = 'default';
}}
/>
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas 放置事件
> 学习如何使用 Konva.js 实现 HTML5 Canvas 放置事件。检测拖动的图形何时被放置到目标区域。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Drop_Events.html
Konva 不支持放置事件,但你可以自行编写放置事件检测逻辑。
如需检测作为放置目标的图形,必须把正在拖动的对象移到另一个图层。
此示例展示 `drop`、`dragenter`、`dragleave` 和 `dragover` 事件的实现。
**操作说明:** 将一个图形拖到另一个图形上方。也可以将一个图形拖放到另一个图形中。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const tempLayer = new Konva.Layer();
stage.add(layer);
stage.add(tempLayer);
const text = new Konva.Text({
fill: 'black',
});
layer.add(text);
let previousShape;
// create multiple stars
for (let i = 0; i < 10; i++) {
const star = new Konva.Star({
x: stage.width() * Math.random(),
y: stage.height() * Math.random(),
fill: 'blue',
numPoints: 10,
innerRadius: 20,
outerRadius: 25,
draggable: true,
name: 'star ' + i,
shadowOffsetX: 5,
shadowOffsetY: 5,
});
star.on('dragstart', () => {
star.moveTo(tempLayer);
text.text('Moving ' + star.name());
});
star.on('dragmove', (e) => {
const pos = stage.getPointerPosition();
const shape = layer.getIntersection(pos);
if (previousShape && shape) {
if (previousShape !== shape) {
// leave from old target
previousShape.fire('dragleave', { evt: e.evt }, true);
// enter new target
shape.fire('dragenter', { evt: e.evt }, true);
previousShape = shape;
} else {
previousShape.fire('dragover', { evt: e.evt }, true);
}
} else if (!previousShape && shape) {
previousShape = shape;
shape.fire('dragenter', { evt: e.evt }, true);
} else if (previousShape && !shape) {
previousShape.fire('dragleave', { evt: e.evt }, true);
previousShape = undefined;
}
});
star.on('dragend', (e) => {
const pos = stage.getPointerPosition();
const shape = layer.getIntersection(pos);
if (previousShape && previousShape !== shape) {
previousShape.fire('dragleave', { evt: e.evt }, true);
}
if (shape) {
shape.fire('drop', { evt: e.evt }, true);
}
previousShape = undefined;
star.moveTo(layer);
});
star.on('dragenter', () => {
star.fill('green');
text.text('dragenter ' + star.name());
});
star.on('dragleave', () => {
star.fill('blue');
text.text('dragleave ' + star.name());
});
star.on('dragover', () => {
text.text('dragover ' + star.name());
});
star.on('drop', () => {
star.fill('red');
text.text('drop ' + star.name());
});
layer.add(star);
}
```
```jsx
import { Stage, Layer, Text, Star } from 'react-konva';
import { useState, useRef } from 'react';
const App = () => {
const [stars, setStars] = useState(() =>
Array.from({ length: 10 }, (_, i) => ({
id: i,
x: window.innerWidth * Math.random(),
y: window.innerHeight * Math.random(),
fill: 'blue',
name: `star ${i}`,
}))
);
const [message, setMessage] = useState('');
const previousShapeRef = useRef(null);
const mainLayerRef = useRef(null);
const tempLayerRef = useRef(null);
const updateStar = (id, attrs) => {
setStars((currentStars) =>
currentStars.map((star) =>
star.id === id ? { ...star, ...attrs } : star
)
);
};
const handleDragStart = (id, e) => {
const shape = e.target;
updateStar(id, { x: shape.x(), y: shape.y() });
shape.moveTo(tempLayerRef.current);
setMessage('Moving ' + shape.name());
};
const handleDragMove = (id, e) => {
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
const shape = mainLayerRef.current.getIntersection(pos);
updateStar(id, { x: e.target.x(), y: e.target.y() });
if (previousShapeRef.current && shape) {
if (previousShapeRef.current !== shape) {
// leave from old target
previousShapeRef.current.fire('dragleave', { evt: e.evt }, true);
// enter new target
shape.fire('dragenter', { evt: e.evt }, true);
previousShapeRef.current = shape;
} else {
previousShapeRef.current.fire('dragover', { evt: e.evt }, true);
}
} else if (!previousShapeRef.current && shape) {
previousShapeRef.current = shape;
shape.fire('dragenter', { evt: e.evt }, true);
} else if (previousShapeRef.current && !shape) {
previousShapeRef.current.fire('dragleave', { evt: e.evt }, true);
previousShapeRef.current = undefined;
}
};
const handleDragEnd = (id, e) => {
const shape = e.target;
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
const dropShape = mainLayerRef.current.getIntersection(pos);
if (
previousShapeRef.current &&
previousShapeRef.current !== dropShape
) {
previousShapeRef.current.fire('dragleave', { evt: e.evt }, true);
}
if (dropShape) {
dropShape.fire('drop', { evt: e.evt }, true);
}
updateStar(id, { x: shape.x(), y: shape.y() });
shape.moveTo(mainLayerRef.current);
previousShapeRef.current = undefined;
};
const handleDragEnter = (id, e) => {
updateStar(id, { fill: 'green' });
setMessage('dragenter ' + e.target.name());
};
const handleDragLeave = (id, e) => {
updateStar(id, { fill: 'blue' });
setMessage('dragleave ' + e.target.name());
};
const handleDragOver = (e) => {
setMessage('dragover ' + e.target.name());
};
const handleDrop = (id, e) => {
updateStar(id, { fill: 'red' });
setMessage('drop ' + e.target.name());
};
return (
{stars.map((star) => (
handleDragStart(star.id, e)}
onDragMove={(e) => handleDragMove(star.id, e)}
onDragEnd={(e) => handleDragEnd(star.id, e)}
onDragEnter={(e) => handleDragEnter(star.id, e)}
onDragLeave={(e) => handleDragLeave(star.id, e)}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(star.id, e)}
/>
))}
);
};
export default App;
```
```vue
```
---
# HTML5 Canvas 简单拖动边界教程
> 学习如何使用 Konva.js 限制 HTML5 Canvas 上的拖动。设置拖动边界,以限制图形可以拖动的区域。
Source: https://konvajs.org/zh-Hans/docs/drag_and_drop/Simple_Drag_Bounds.html
如需限制使用 Konva 拖放的图形的移动范围,
可以使用 `dragmove` 事件,并在事件处理程序中重新设置拖放位置。
可以使用此事件以多种方式限制拖放时的移动。例如,只允许水平、垂直、对角线或径向移动,甚至可以将节点
限制在方框、圆形或其他路径内。
```js
shape.on('dragmove', () => {
// lock position of the shape on x axis
// keep y position as is
shape.x(0);
});
```
_提示:你可以使用 `shape.absolutePosition()` 方法获取或设置节点的绝对位置,而不是使用相对的 `x` 和 `y`。_
**操作说明:** 拖放横向文本,并观察它只能沿水平方向移动。
拖放纵向文本,并观察它只能沿垂直方向移动。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const horizontalText = new Konva.Text({
x: 50,
y: 50,
text: 'Drag me horizontally',
fontSize: 16,
draggable: true,
fill: 'black',
});
horizontalText.on('dragmove', function () {
// horizontal only
this.y(50);
});
const verticalText = new Konva.Text({
x: 200,
y: 50,
text: 'Drag me vertically',
fontSize: 16,
draggable: true,
fill: 'black',
});
verticalText.on('dragmove', function () {
// vertical only
this.x(200);
});
layer.add(horizontalText);
layer.add(verticalText);
```
```jsx
import { Stage, Layer, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [horizontalPosition, setHorizontalPosition] = useState({ x: 50, y: 50 });
const [verticalPosition, setVerticalPosition] = useState({ x: 200, y: 50 });
const handleHorizontalDragMove = (e) => {
// The y prop stays 50, so React-Konva will not reapply it after a drag.
// Constrain the node directly, then save its actual position in state.
e.target.y(50);
setHorizontalPosition(e.target.position());
};
const handleVerticalDragMove = (e) => {
// The x prop stays 200, so a state update alone cannot lock this axis.
e.target.x(200);
setVerticalPosition(e.target.position());
};
return (
);
};
export default App;
```
```vue
```
---
# 使用 Konva 取消 HTML5 Canvas 事件冒泡传播
> 学习如何将事件对象的 cancelBubble 设置为 true,以取消 Konva 中的事件冒泡传播。
Source: https://konvajs.org/zh-Hans/docs/events/Cancel_Propagation.html
要取消 Konva 中的事件冒泡传播,可以将事件对象的 `cancelBubble`
属性设置为 true。
**操作说明:单击圆形。你会看到仅处理了圆形绑定的事件,因为触发圆形事件时取消了事件传播,
所以事件对象不会向上冒泡。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
circle.on('click', function (evt) {
alert('You clicked on the circle');
// stop event bubble
evt.cancelBubble = true;
});
layer.on('click', function () {
alert('You clicked on the layer');
});
layer.add(circle);
```
```jsx
import { Stage, Layer, Circle } from 'react-konva';
const App = () => {
const handleCircleClick = (e) => {
alert('You clicked on the circle');
// stop event bubble
e.cancelBubble = true;
};
const handleLayerClick = () => {
alert('You clicked on the layer');
};
return (
);
};
export default App;
```
```html
```
---
# HTML5 Canvas 自定义命中检测函数教程
> 学习如何使用 Konva.js 为 HTML5 Canvas 图形定义自定义命中检测区域,以及如何使用 hitFunc 覆盖默认命中区域以准确检测单击。
Source: https://konvajs.org/zh-Hans/docs/events/Custom_Hit_Region.html
可以使用两种方式更改图形的命中区域:`hitFunc` 和 `hitStrokeWidth` 属性。
## 1. 什么是 `hitFunc`?
要使用 Konva 为图形创建自定义命中绘制函数,可以设置
`hitFunc` 属性。Konva 使用命中绘制函数绘制用于命中检测的区域。
自定义命中绘制函数有多种用途。例如,你可以扩大命中区域,
让用户更容易与图形交互。你也可以检测图形的某些部分并忽略其他部分,
或简化命中绘制函数以提高渲染性能。
另请参阅编写自定义 `sceneFunc` 的[最佳实践](/zh-Hans/docs/shapes/Custom.html),这些实践也适用于 `hitFunc`。
`hitFunc` 是一个有两个参数的函数:一个 [Konva.Context](/zh-Hans/api/Konva.Context.html) 渲染器和一个图形实例。
## 2. 什么是 `hitStrokeWidth`?
对于 `Konva.Line` 等图形,覆盖 `hitFunc` 很困难。有时,你只想加粗事件命中区域。此时,最好为 `hitStrokeWidth` 属性设置较大的值。
**操作说明:在星形上触发 mouseover、mouseout、mousedown 和 mouseup。
你会看到命中区域是一个包围图形的大圆。也请对线条执行相同操作。
你还可以切换命中 Canvas 来查看其外观。这对调试很有用。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 10,
y: 10,
text: '',
fontSize: 24,
});
layer.add(text);
const star = new Konva.Star({
x: stage.width() / 4,
y: stage.height() / 2,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
// custom hit function
star.hitFunc(function (context) {
context.beginPath();
context.arc(0, 0, 70, 0, Math.PI * 2, true);
context.closePath();
context.fillStrokeShape(this);
});
const line = new Konva.Line({
x: stage.width() * 0.6,
y: stage.height() / 2,
points: [-50, -50, 50, 50],
stroke: 'black',
strokeWidth: 2,
hitStrokeWidth: 20,
});
const button = document.createElement('button');
button.innerHTML = 'Toggle hit canvas';
document.body.appendChild(button);
let showHit = false;
button.addEventListener('click', () => {
showHit = !showHit;
if (showHit) {
stage.container().style.border = '2px solid black';
stage.container().style.height = stage.height() + 'px';
stage.container().appendChild(layer.hitCanvas._canvas);
layer.hitCanvas._canvas.style.position = 'absolute';
layer.hitCanvas._canvas.style.top = 0;
layer.hitCanvas._canvas.style.left = 0;
} else {
layer.hitCanvas._canvas.remove();
}
});
function writeMessage(message) {
text.text(message);
}
star.on('mouseover mouseout mousedown mouseup', function (evt) {
writeMessage(evt.type + ' star');
});
line.on('mouseover mouseout mousedown mouseup', function (evt) {
writeMessage(evt.type + ' line');
});
layer.add(star);
layer.add(line);
```
```jsx
import { Stage, Layer, Star, Line, Text } from 'react-konva';
import { useState, useEffect } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const [showHit, setShowHit] = useState(false);
const handleStarEvent = (evt) => {
setMessage(evt.type + ' star');
};
const handleLineEvent = (evt) => {
setMessage(evt.type + ' line');
};
useEffect(() => {
const stage = document.querySelector('.konvajs-content');
if (showHit) {
const hitCanvas = stage.querySelector('canvas:last-child');
stage.style.border = '2px solid black';
hitCanvas.style.position = 'absolute';
hitCanvas.style.top = '0';
hitCanvas.style.left = '0';
}
}, [showHit]);
return (
<>
{
context.beginPath();
context.arc(0, 0, 70, 0, Math.PI * 2, true);
context.closePath();
context.fillStrokeShape(shape);
}}
onMouseover={handleStarEvent}
onMouseout={handleStarEvent}
onMousedown={handleStarEvent}
onMouseup={handleStarEvent}
/>
>
);
};
export default App;
```
```html
```
---
# HTML5 Canvas 桌面端和移动端事件支持教程
> 学习如何在 Konva 中使用 mousedown/touchstart 和 mouseup/touchend 等事件对,同时处理桌面端和移动端事件。
Source: https://konvajs.org/zh-Hans/docs/events/Desktop_and_Mobile.html
_注意:此示例可能已过时,因为现代浏览器支持指针事件。Konva 也支持指针事件。请参阅[指针事件示例](/zh-Hans/docs/events/Pointer_Events.html)。如果你不想使用指针事件,请继续阅读。_
要使用 Konva 为图形添加同时适用于桌面端和移动端应用的事件处理程序,可以使用 `on()` 方法并传入事件对。
例如,要在桌面端和移动端应用中触发 `mousedown` 事件,可以使用 `"mousedown touchstart"` 事件对来覆盖这两种环境。
要在桌面端和移动端应用中触发 `mouseup` 事件,可以使用 `"mouseup touchend"` 事件对。
还可以使用 `"dblclick dbltap"` 事件对绑定同时适用于桌面端和移动端设备的双击事件。
**操作说明:在桌面端或移动端设备上,对圆形触发 mousedown、mouseup、touchstart 或 touchend,以观察相同的功能。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
function writeMessage(message) {
text.text(message);
}
// desktop and mobile events
circle.on('mousedown touchstart', function () {
writeMessage('Mousedown or touchstart');
});
circle.on('mouseup touchend', function () {
writeMessage('Mouseup or touchend');
});
layer.add(circle);
layer.add(text);
stage.add(layer);
```
```jsx
import { Stage, Layer, Circle, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [message, setMessage] = useState('');
return (
setMessage('Mousedown or touchstart')}
onTouchstart={() => setMessage('Mousedown or touchstart')}
onMouseup={() => setMessage('Mouseup or touchend')}
onTouchend={() => setMessage('Mouseup or touchend')}
/>
);
};
export default App;
```
```html
```
---
# 使用 Konva 实现 HTML5 Canvas 事件委托
> 学习如何使用 Konva.js 在 HTML5 Canvas 上实现事件委托。在图层或组上监听事件,而不是在各个图形上监听,以简化代码。
Source: https://konvajs.org/zh-Hans/docs/events/Event_Delegation.html
要使用 Konva 获取事件目标,可以访问事件对象的 `target` 属性。
这对事件委托特别有用。我们可以将事件处理程序绑定到父节点,
并监听其子节点上发生的事件。
**操作说明:单击星形,观察图层绑定的事件是否正确识别被单击的图形。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
layer.add(text);
const star = new Konva.Star({
x: stage.width() / 2,
y: stage.height() / 2,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
layer.add(star);
// add event delegation
layer.on('click', function (evt) {
const shape = evt.target;
text.text('click on ' + shape.getClassName());
});
```
```jsx
import { Stage, Layer, Star, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const handleLayerClick = (e) => {
const shape = e.target;
setMessage('click on ' + shape.getClassName());
};
return (
);
};
export default App;
```
```html
```
---
# 使用 Konva 触发 HTML5 Canvas 事件
> 学习如何使用 fire() 方法以编程方式在 Konva 图形上触发事件,包括自定义事件。
Source: https://konvajs.org/zh-Hans/docs/events/Fire_Events.html
要使用 Konva 触发事件,可以使用 `fire()` 方法。
此方法可以通过编程方式触发 `click`、`mouseover`、`mousemove` 等事件,
也可以触发 foo 和 bar 等自定义事件。
> **注意**:虽然可以使用自定义事件,但通常最好使用 `click`、`mouseover`、`mousemove` 等内置交互事件。自定义事件会增加代码的维护和调试难度。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
// add shape event listener
circle.on('customEvent', function (evt) {
alert('custom event fired');
});
// add button to trigger custom event
const button = document.createElement('button');
button.innerHTML = 'Fire Custom Event';
button.style.position = 'absolute';
button.style.top = '10px';
button.style.left = '10px';
button.style.zIndex = '1';
document.body.appendChild(button);
button.addEventListener('click', () => {
// fire custom event
circle.fire('customEvent', {
bubbles: true,
});
});
layer.add(circle);
```
```jsx
import { Stage, Layer, Circle } from 'react-konva';
import { useRef } from 'react';
const App = () => {
const circleRef = useRef();
const handleCustomEvent = () => {
alert('custom event fired');
};
const fireCustomEvent = () => {
circleRef.current.fire('customevent', {
bubbles: true,
});
};
return (
<>
>
);
};
export default App;
```
```html
```
---
# 使用 Konva 处理 HTML5 Canvas 图像事件
> 学习如何使用 Konva.js 处理 HTML5 Canvas 中的图像事件,以及如何为 Konva.Image 图形绑定 click、mouseover 和触摸事件。
Source: https://konvajs.org/zh-Hans/docs/events/Image_Events.html
要使用 Konva 仅检测图像中非透明像素上的事件,可以使用 `drawHitFromCache()` 方法生成更准确的图像命中区域。
默认情况下,图像内的任何像素都可以触发事件,即使该像素是透明的。`drawHitFromCache()` 方法还接受一个可选的回调方法。每次创建图像命中区域时,都会执行此方法。
*注意:`drawHitFromCache()` 方法要求图像和执行代码托管在同一域名下的 Web 服务器上。*
**操作说明:将鼠标移到猴子和狮子上,观察绑定的 mouseover 事件。对于猴子,只要鼠标移到图像的任何部分,事件就会触发,包括透明像素。因为我们为狮子创建了图像命中区域,所以会忽略透明像素,从而实现更准确的事件检测。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
layer.add(text);
function writeMessage(message) {
text.text(message);
}
const imageObj1 = new Image();
imageObj1.onload = () => {
const monkey = new Konva.Image({
x: 120,
y: 50,
image: imageObj1,
width: 200,
height: 200,
});
monkey.on('mouseover', function () {
writeMessage('mouseover monkey (regular image)');
});
monkey.on('mouseout', function () {
writeMessage('');
});
layer.add(monkey);
};
imageObj1.crossOrigin = 'Anonymous';
imageObj1.src = 'https://konvajs.org/assets/monkey.png';
const imageObj2 = new Image();
imageObj2.onload = () => {
const lion = new Konva.Image({
x: 320,
y: 50,
image: imageObj2,
width: 200,
height: 200,
});
// override color detection region
lion.on('mouseover', function () {
writeMessage('mouseover lion (with transparent pixels detection)');
});
lion.on('mouseout', function () {
writeMessage('');
});
layer.add(lion);
lion.cache();
lion.drawHitFromCache();
};
imageObj2.crossOrigin = 'Anonymous';
imageObj2.src = 'https://konvajs.org/assets/lion.png';
```
```jsx
import { Stage, Layer, Image, Text } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [message, setMessage] = useState('');
const lionRef = useRef();
const [monkeyImage] = useImage('https://konvajs.org/assets/monkey.png', 'anonymous');
const [lionImage] = useImage('https://konvajs.org/assets/lion.png', 'anonymous');
useEffect(() => {
if (lionImage) {
lionRef.current.cache();
lionRef.current.drawHitFromCache();
}
}, [lionImage]);
return (
{monkeyImage && (
setMessage('mouseover monkey (regular image)')}
onMouseout={() => setMessage('')}
/>
)}
{lionImage && (
setMessage('mouseover lion (with transparent pixels detection)')
}
onMouseout={() => setMessage('')}
/>
)}
);
};
export default App;
```
```html
```
---
# 使用 Konva 处理 HTML5 Canvas 键盘事件
> 学习如何使用 Konva.js 处理 HTML5 Canvas 图形上的键盘事件,以及如何实现键盘快捷键、方向键移动和按键交互。
Source: https://konvajs.org/zh-Hans/docs/events/Keyboard_Events.html
Konva 没有 `keydown` 或 `keyup` 等内置键盘事件。
### 如何监听 Canvas 上的 keydown 或 keyup 事件?
你可以通过两种方式添加这些事件:
1. 监听 `window` 对象上的全局事件
2. 使用 `tabIndex` 属性让舞台容器可获得焦点,然后监听容器上的事件。
**操作说明:单击舞台以使其获得焦点,然后使用方向键移动图形。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 50,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
layer.add(circle);
// make stage container focusable
stage.container().tabIndex = 1;
// focus it
// also stage will be in focus on its click
stage.container().focus();
const DELTA = 4;
// add keyboard events
stage.container().addEventListener('keydown', (e) => {
if (e.keyCode === 37) {
circle.x(circle.x() - DELTA);
} else if (e.keyCode === 38) {
circle.y(circle.y() - DELTA);
} else if (e.keyCode === 39) {
circle.x(circle.x() + DELTA);
} else if (e.keyCode === 40) {
circle.y(circle.y() + DELTA);
} else {
return;
}
e.preventDefault();
});
```
```jsx
import { Stage, Layer, Circle } from 'react-konva';
import { useRef, useEffect, useState } from 'react';
const App = () => {
const stageRef = useRef();
const containerRef = useRef();
const [position, setPosition] = useState({
x: window.innerWidth / 2,
y: window.innerHeight / 2,
});
useEffect(() => {
// focus the div on mount
containerRef.current.focus();
}, []);
const handleKeyDown = (e) => {
const DELTA = 4;
switch (e.keyCode) {
case 37: // left
setPosition(pos => ({ ...pos, x: pos.x - DELTA }));
break;
case 38: // up
setPosition(pos => ({ ...pos, y: pos.y - DELTA }));
break;
case 39: // right
setPosition(pos => ({ ...pos, x: pos.x + DELTA }));
break;
case 40: // down
setPosition(pos => ({ ...pos, y: pos.y + DELTA }));
break;
default:
return;
}
e.preventDefault();
};
return (
);
};
export default App;
```
```html
```
---
# 使用 Konva 启用或禁用 HTML5 Canvas 事件监听
> 学习如何使用 listening 属性和 setListening() 方法启用或禁用 Konva 图形上的事件监听。
Source: https://konvajs.org/zh-Hans/docs/events/Listen_for_Events.html
要使用 Konva 启用或禁用事件监听,可以在实例化图形时将配置对象的 listening
属性设置为 true 或 false,也可以使用 `setListening()` 方法设置 listening 属性。
为一个或多个节点设置 listening 属性后,还需要使用 `drawHit()` 方法
重新绘制每个受影响图层的命中图。
**操作说明:将鼠标移到椭圆上,观察事件处理程序是否未执行。
单击“Listen”开始监听事件,然后观察事件处理程序是否开始执行。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
layer.add(text);
const oval = new Konva.Ellipse({
x: stage.width() / 2,
y: stage.height() / 2,
radiusX: 100,
radiusY: 50,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
listening: false,
});
oval.on('mouseover', function () {
writeMessage('Mouseover oval');
});
oval.on('mouseout', function () {
writeMessage('');
});
function writeMessage(message) {
text.text(message);
}
layer.add(oval);
// add button to toggle listening
const button = document.createElement('button');
button.innerHTML = 'Listen';
document.body.appendChild(button);
button.addEventListener('click', () => {
const listening = !oval.listening();
oval.listening(listening);
button.innerHTML = listening ? 'Stop listening' : 'Listen';
layer.drawHit();
});
```
```jsx
import { Stage, Layer, Ellipse, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const [listening, setListening] = useState(false);
return (
<>
setMessage('Mouseover oval')}
onMouseout={() => setMessage('')}
/>
>
);
};
export default App;
```
```html
```
---
# HTML5 Canvas 移动端触摸事件教程
> 学习如何使用 Konva.js 在移动设备上处理 HTML5 Canvas 触摸事件,包括 tap、touchstart、touchmove、touchend 和多点触摸手势。
Source: https://konvajs.org/zh-Hans/docs/events/Mobile_Events.html
要使用 Konva 在移动设备上为图形绑定事件处理程序,可以使用 `on()` 方法。
`on()` 方法需要一个事件类型,以及一个在事件发生时执行的函数。
Konva 支持 `touchstart`、`touchmove`、`touchend`、`tap`、`dbltap`、`dragstart`、`dragmove` 和 `dragend` 移动端事件。
有关 `rotate` 等更复杂的手势,请参阅[手势示例](/zh-Hans/docs/sandbox/Gestures.html)。
如果你需要整个舞台的平移和缩放逻辑,请参阅[多点触摸缩放舞台示例](/zh-Hans/docs/sandbox/Multi-touch_Scale_Stage.html)。
*注意:此示例使用触摸事件而不是鼠标事件,因此仅适用于移动设备。*
**操作说明:在三角形上移动手指以查看触摸坐标,并在圆形上触发 touchstart 和 touchend。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
const triangle = new Konva.RegularPolygon({
x: 80,
y: 120,
sides: 3,
radius: 80,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
});
const circle = new Konva.Circle({
x: 230,
y: 100,
radius: 60,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
function writeMessage(message) {
text.text(message);
}
triangle.on('touchmove', function () {
const touchPos = stage.getPointerPosition();
const x = touchPos.x;
const y = touchPos.y;
writeMessage('x: ' + x + ', y: ' + y);
});
circle.on('touchstart', function () {
writeMessage('touchstart circle');
});
circle.on('touchend', function () {
writeMessage('touchend circle');
});
layer.add(triangle);
layer.add(circle);
layer.add(text);
stage.add(layer);
```
```jsx
import { Stage, Layer, RegularPolygon, Circle, Text } from 'react-konva';
import { useState, useRef } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const stageRef = useRef();
const handleTriangleTouch = () => {
const touchPos = stageRef.current.getPointerPosition();
setMessage(`x: ${touchPos.x}, y: ${touchPos.y}`);
};
return (
setMessage('touchstart circle')}
onTouchend={() => setMessage('touchend circle')}
/>
);
};
export default App;
```
```html
```
---
# 使用 Konva 控制 HTML5 Canvas 移动端滚动和原生事件
> 学习如何使用图形的 preventDefault 属性控制 Konva 舞台上的移动端滚动行为。
Source: https://konvajs.org/zh-Hans/docs/events/Mobile_Scrolling.html
默认情况下,`Konva` 会阻止与舞台进行的所有指针交互的默认行为。
这会防止你在移动设备上拖放图形时意外滚动页面。
但在某些情况下,你可能要保留浏览器事件的默认行为。此时,可以将图形的 `preventDefault` 属性设置为 `false`。
**操作说明:如果你使用移动设备,请尝试通过每个矩形滚动页面。
绿色矩形会阻止默认行为(页面不滚动)。
红色矩形会保留默认行为(页面可以滚动)。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// green rectangle - will prevent scrolling
const greenRect = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 600,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
});
layer.add(greenRect);
// red rectangle - will NOT prevent scrolling
const redRect = new Konva.Rect({
x: 200,
y: 50,
width: 100,
height: 600,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
preventDefault: false,
});
layer.add(redRect);
```
```jsx
import { Stage, Layer, Rect } from 'react-konva';
const App = () => {
return (
);
};
export default App;
```
```html
```
---
# 为什么处理函数在移动端触发两次,以及拖动之后的 click
> 在 Konva 中一次点按可能触发 tap、pointerclick 和 click,因此绑定到 'click tap' 的处理函数在触摸设备上会执行两次。本文说明如何只绑定一个事件族,以及为什么拖动之后 click 本来就不会触发。
Source: https://konvajs.org/zh-Hans/docs/events/Mobile_Tap_And_Click.html
关于 Canvas 上的指针输入,有两件事常让人意外。一件是你必须处理的真实问题;另一件已经被
处理好了,知道这一点可以让你省掉一个并不需要的变通方案。
## 一次点按可能触发三个事件
Konva 会监听全部三个 DOM 输入事件族——鼠标、触摸和指针——这样处理函数无需你事先选择就能
在各处工作。但它不会把它们合并。
在触摸屏上,浏览器会发送一组 `touchstart`/`touchend`、一组 `pointerdown`/`pointerup`,
然后为了兼容触摸出现之前编写的页面,再**合成**一组 `mousedown`/`mouseup`。Konva 会把它们
各自转换成自己的事件。一次点按会产生:
| Konva 事件 | 来源 |
| --- | --- |
| `tap` | 触摸事件对 |
| `pointerclick` | 指针事件对 |
| `click` | 合成的鼠标事件对 |
因此下面这段代码在手机上会让处理函数执行**两次**,在桌面端执行一次:
```js
// Fires for `click` and again for `tap` on a touch device.
shape.on('click tap', handleSelect);
```
### 只绑定一个事件族
最简单的办法是不再成对绑定事件,改用指针事件族——它已经覆盖了鼠标、触摸和触控笔:
```js
shape.on('pointerclick', handleSelect); // once, on every device
```
`pointerdown`、`pointermove` 和 `pointerup` 的用法相同。指针事件默认开启
(`Konva.pointerEventsEnabled`),更多内容参见
[指针事件](/zh-Hans/docs/events/Pointer_Events.html)。
如果你需要支持不带指针事件的浏览器,或者正在维护已经成对绑定 `click tap` 的代码,
那就为处理函数加一道防护:
```js
let lastHandled = 0;
function handleSelect(e) {
// A synthesised mouse event follows its touch within about 300ms.
const now = Date.now();
if (now - lastHandled < 400) return;
lastHandled = now;
// ...
}
```
请优先只绑定一个事件族。计时器只是变通方案,它会吞掉手速快的用户真正的第二次点按。
## 拖动之后 click 本来就不会触发
这一点值得了解,因为常见的变通方案其实没有必要。
当拖动真正开始时,Konva 会清除允许派发 click 的标志,因此 `dragend` 之后**不会**
再触发 `click`:
```js
shape.on('dragend', () => console.log('dragged'));
shape.on('click', () => console.log('clicked'));
// Drag the shape: you get 'dragged' only.
// Press and release without moving: you get 'clicked' only.
```
如果你仍然看到拖动之后出现 click,通常是下面几种原因之一。
**你监听的是 `mouseup` 而不是 `click`。** `mouseup` 是原始指针事件,无论是否拖动都会触发。
Konva 抑制的是 `click`。
**指针的移动距离不足以算作拖动。** `Konva.dragDistance` 默认为 `3` 像素。按下时偏移
一两个像素——这在触摸屏上很正常——会被当作一次点击,`dragstart` 也不会触发。这是预期行为。
如果你的用户需要在很小的目标上操作且手不够稳,可以调高它:
```js
Konva.dragDistance = 8;
```
**你把 `dragDistance` 设成了 `0`。** 那么每次按下都是拖动,于是你想要的 click 被抑制了。
## 阻止浏览器自身的手势
与 Konva 无关的是,在 Canvas 上触摸可能会滚动页面或触发双击缩放。那是浏览器的行为,
需要用 CSS 阻止:
```css
#container {
touch-action: none;
}
```
如果页面仍应可以垂直滚动、同时由 Canvas 处理水平拖动,请改用 `touch-action: pan-y`。
完整说明参见[移动端滚动](/zh-Hans/docs/events/Mobile_Scrolling.html)。
## 相关内容
- [指针事件](/zh-Hans/docs/events/Pointer_Events.html)——适用于所有设备的单一事件族
- [移动端事件](/zh-Hans/docs/events/Mobile_Events.html)——触摸事件列表
- [桌面端与移动端](/zh-Hans/docs/events/Desktop_and_Mobile.html)——较早的成对绑定做法
---
# HTML5 Canvas 多事件绑定教程
> 学习如何在 Konva 中使用 on() 方法和空格分隔的字符串,将多个事件绑定到一个处理程序。
Source: https://konvajs.org/zh-Hans/docs/events/Multi_Event.html
要使用 Konva 将多个事件绑定到一个处理程序,可以使用 `on()` 方法,并传入包含多个事件类型且以空格分隔的字符串。
```js
shape.on('mouseover mousedown mouseup', function (e) {
console.log('events: ' + e.type);
});
```
**操作说明:在圆形上触发 mouseover、mousedown 和 mouseup,观察绑定到圆形的函数是否为每个事件执行。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
layer.add(text);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
function writeMessage(message) {
text.text(message);
}
circle.on('mouseover mousedown mouseup', function (evt) {
writeMessage('event: ' + evt.type);
});
layer.add(circle);
```
```jsx
import { Stage, Layer, Circle, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const handleMultiEvent = (e) => {
setMessage('event: ' + e.type);
};
return (
);
};
export default App;
```
```html
```
---
# HTML5 Canvas 指针事件教程
> 学习如何在 Konva 中使用指针事件,通过一个事件处理程序同时处理鼠标和触摸输入。
Source: https://konvajs.org/zh-Hans/docs/events/Pointer_Events.html
指针事件可用于通过一个处理程序同时处理移动端和桌面端事件。
要使用 Konva 为图形绑定指针事件处理程序,可以使用 `on()` 方法。
`on()` 方法需要一个事件类型,以及一个在事件发生时执行的函数。
Konva 支持 `pointerdown`、`pointermove`、`pointerup`、`pointercancel`、`pointerover`、`pointerenter`、`pointerout`、`pointerleave`、`pointerclick`、`pointerdblclick` 事件。
_注意:此示例同时适用于移动端和桌面端设备。_
**操作说明:在三角形上移动鼠标或手指,以查看指针坐标。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
const triangle = new Konva.RegularPolygon({
x: stage.width() / 2,
y: stage.height() / 2,
sides: 3,
radius: 80,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
});
function writeMessage(message) {
text.text(message);
}
triangle.on('pointermove', function () {
const pos = stage.getPointerPosition();
writeMessage('x: ' + pos.x + ', y: ' + pos.y);
});
triangle.on('pointerout', function () {
writeMessage('');
});
layer.add(triangle);
layer.add(text);
stage.add(layer);
```
```jsx
import { Stage, Layer, RegularPolygon, Text } from 'react-konva';
import { useState, useRef } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const stageRef = useRef();
const handlePointerMove = () => {
const pos = stageRef.current.getPointerPosition();
setMessage(`x: ${pos.x}, y: ${pos.y}`);
};
return (
setMessage('')}
/>
);
};
export default App;
```
```html
```
---
# 使用 Konva 移除 HTML5 Canvas 事件监听器
> 学习如何使用 off() 方法从 Konva 图形中移除事件监听器。
Source: https://konvajs.org/zh-Hans/docs/events/Remove_Event.html
要使用 Konva 移除事件监听器,可以使用图形对象的 `off()` 方法。
此方法需要 click 或 mousedown 等事件类型。
**操作说明:单击圆形,查看 onclick 事件绑定触发的警告框。
单击按钮以移除事件监听器,然后再次单击圆形,观察事件绑定是否已移除。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
// add click listener
circle.on('click', function () {
alert('you clicked the circle');
});
layer.add(circle);
// add button to remove listener
const button = document.createElement('button');
button.style.position = 'absolute';
button.style.top = '10px';
button.style.left = '10px';
button.innerHTML = 'Remove click listener';
document.body.appendChild(button);
button.addEventListener('click', () => {
// remove click listener
circle.off('click');
});
```
```jsx
import { Stage, Layer, Circle } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [hasListener, setHasListener] = useState(true);
return (
<>
alert('you clicked the circle') : null}
/>
>
);
};
export default App;
```
```html
```
---
# 使用 Konva 按名称移除 HTML5 Canvas 事件监听器
> 学习如何在 Konva 中配合命名事件使用 on() 和 off() 方法,按命名空间移除特定的事件监听器。
Source: https://konvajs.org/zh-Hans/docs/events/Remove_by_Name.html
Konva 事件命名空间用于在命令式代码中标识相关的监听器。将命名空间添加到事件类型后,
例如 `click.menu`。然后将相同名称传给 `off()`,以移除该监听器。
**操作说明:**选择圆形以运行两个监听器。分别使用两个按钮移除一个监听器。
然后再次选择圆形。
`react-konva` 不通过 React 事件 props 公开 Konva 事件命名空间。
React 为每种事件类型提供一个 prop,例如 `onClick`。在 React 中存储启用状态,
并根据条件向该 prop 传入一个分派函数。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
// add click listeners
circle.on('click.event1', function () {
alert('first click listener');
});
circle.on('click.event2', function () {
alert('second click listener');
});
layer.add(circle);
// add buttons to remove listeners
const button1 = document.createElement('button');
button1.innerHTML = 'Remove first listener';
button1.style.position = 'absolute';
button1.style.top = '0';
button1.style.left = '0';
button1.onclick = function() {
circle.off('click.event1');
};
document.getElementById('container').appendChild(button1);
const button2 = document.createElement('button');
button2.innerHTML = 'Remove second listener';
button2.style.position = 'absolute';
button2.style.top = '30px';
button2.style.left = '0';
button2.onclick = function() {
circle.off('click.event2');
};
document.getElementById('container').appendChild(button2);
```
```jsx
import { useState } from 'react';
import { Stage, Layer, Circle, Text } from 'react-konva';
const App = () => {
const [firstEnabled, setFirstEnabled] = useState(true);
const [secondEnabled, setSecondEnabled] = useState(true);
const [message, setMessage] = useState('Select the circle');
const handleClick =
firstEnabled || secondEnabled
? () => {
const messages = [];
if (firstEnabled) {
messages.push('first listener');
}
if (secondEnabled) {
messages.push('second listener');
}
setMessage(messages.join(' + '));
}
: undefined;
return (
<>
>
);
};
export default App;
```
---
# Konva 的 HTML5 Canvas 特殊舞台事件
> 了解 Konva.js 中特殊的舞台级事件,包括 contentClick、contentMousemove 和其他在 Stage 容器上触发的事件。
Source: https://konvajs.org/zh-Hans/docs/events/Stage_Events.html
所有事件都从图形开始。因此,如果单击 Canvas 内的空白区域,`click` 事件不会在 `Layer` 上触发,而会在 `Stage` 对象上触发。
**操作说明:单击空白区域和图形,查看不同的事件行为。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const text = new Konva.Text({
x: 10,
y: 10,
fontFamily: 'Calibri',
fontSize: 24,
text: '',
fill: 'black',
});
layer.add(text);
const circle = new Konva.Circle({
x: stage.width() / 2,
y: stage.height() / 2,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
function writeMessage(message) {
text.text(message);
}
// handle stage click
stage.on('click', function (e) {
if (e.target === stage) {
writeMessage('clicked on stage');
return;
}
writeMessage('clicked on ' + e.target.name());
});
// add shape
circle.name('circle');
layer.add(circle);
```
```jsx
import { Stage, Layer, Circle, Text } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const handleStageClick = (e) => {
if (e.target === e.target.getStage()) {
setMessage('clicked on stage');
return;
}
setMessage('clicked on ' + e.target.name());
};
return (
);
};
export default App;
```
```html
```
---
# HTML5 Canvas 图像增亮滤镜教程
> 已弃用的 Konva.js Brighten 滤镜教程。请改用较新的 Brightness 滤镜调整图像亮度。
Source: https://konvajs.org/zh-Hans/docs/filters/Brighten.html
**注意**:此滤镜已弃用,将来会移除。请改用 `Brightness` 滤镜。
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 增亮或调暗图像,可以使用 `Konva.Filters.Brighten`
滤镜,并通过 `brightness` 属性设置亮度。
`brightness` 属性可以设置为 -1 到 1 之间的任意数值。
负值会调暗图像,正值会增亮图像。
**操作说明**:滑动控件以调整亮度。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Brighten]);
image.brightness(0.3);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '-1';
slider.max = '1';
slider.step = '0.1';
slider.value = image.brightness();
slider.style.position = 'absolute';
slider.style.top = '20px';
slider.style.left = '20px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image.brightness(value);
});
document.body.appendChild(slider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [brightness, setBrightness] = useState(0.3);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Brighten]}
brightness={brightness}
/>
setBrightness(parseFloat(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像亮度滤镜教程
> 学习如何使用 Konva.js Brightness 滤镜调整 HTML5 Canvas 图像的亮度,其效果与 CSS 的 brightness 滤镜类似。
Source: https://konvajs.org/zh-Hans/docs/filters/Brightness.html
**注意**:Konva 10.0.0 引入了此滤镜,用它替代 `Brighten` 滤镜。为保持向后兼容,`Brighten` 滤镜仍然可用,但它已弃用,将来会移除。
新滤镜的渲染结果更接近 CSS `filter: brightness(0.5);`。
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 增亮或调暗图像,可以使用 `Konva.Filters.Brightness`
滤镜,并通过 `brightness` 属性设置亮度。
`brightness` 属性可以设置为 0 到 2 之间的任意数字,其中:
- 0 生成全黑图像
- 1 表示原始图像(无变化)
- 大于 1 的值会增亮图像
- 2 生成非常明亮的图像
**操作说明**:滑动控件以调整亮度。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Brightness]);
image.brightness(1.5);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '2';
slider.step = '0.1';
slider.value = image.brightness();
slider.style.position = 'absolute';
slider.style.top = '20px';
slider.style.left = '20px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image.brightness(value);
});
document.body.appendChild(slider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [brightness, setBrightness] = useState(1.5);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Brightness]}
brightness={brightness}
/>
setBrightness(parseFloat(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像对比度滤镜教程
> 学习如何使用 Konva.js Contrast 滤镜和交互式滑块示例,调整 HTML5 Canvas 图像的对比度。
Source: https://konvajs.org/zh-Hans/docs/filters/Contrast.html
要对 `Konva.Node` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 更改图像对比度,可以使用 `Konva.Filters.Contrast`。
**操作说明**:滑动控件以更改对比度值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Contrast]);
image.contrast(30);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '-100';
slider.max = '100';
slider.value = image.contrast();
slider.style.position = 'absolute';
slider.style.top = '20px';
slider.style.left = '20px';
slider.addEventListener('input', (e) => {
const value = parseInt(e.target.value);
image.contrast(value);
});
document.body.appendChild(slider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [contrast, setContrast] = useState(30);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Contrast]}
contrast={contrast}
/>
setContrast(parseInt(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 自定义滤镜教程
> 学习如何通过直接处理 Canvas ImageData 像素,在 Konva.js 中创建并应用自定义图像滤镜。
Source: https://konvajs.org/zh-Hans/docs/filters/Custom_Filter.html
## 如何为 Konva 节点应用自定义滤镜?
本示例展示如何在 `Konva` 框架中使用自定义滤镜。
`Filter` 是一个以 Canvas ImageData 为输入的函数,它必须修改该数据。
```javascript
function Filter(imageData) {
// do something with image data
imageData.data[0] = 0;
}
```
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
有关自定义滤镜示例,另请参阅 [图像边框示例](/zh-Hans/docs/sandbox/Image_Border.html)。
**本示例将移除图像的全部透明度。**
```js
import Konva from 'konva';
// create our custom filter
Konva.Filters.RemoveAlpha = function (imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i + 3] = 255; // set alpha to 1
}
};
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.RemoveAlpha]);
};
imageObj.src = 'https://konvajs.org/assets/lion.png';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
// create our custom filter
Konva.Filters.RemoveAlpha = function (imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i + 3] = 255; // set alpha to 1
}
};
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [image] = useImage('https://konvajs.org/assets/lion.png', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image) {
imageRef.current.cache();
}
}, [image]);
return (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.RemoveAlpha]}
/>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像浮雕滤镜教程
> 学习如何使用 Konva.js 对 HTML5 Canvas 图像应用浮雕滤镜,并调整强度、白色级别和混合参数。
Source: https://konvajs.org/zh-Hans/docs/filters/Emboss.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
**操作说明**:滑动控件以更改浮雕参数值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Emboss]);
image.embossStrength(0.5);
image.embossWhiteLevel(0.5);
image.embossDirection('top-left');
image.embossBlend(0.5);
// create sliders
const strengthSlider = document.createElement('input');
strengthSlider.type = 'range';
strengthSlider.min = '0';
strengthSlider.max = '1';
strengthSlider.step = '0.1';
strengthSlider.value = image.embossStrength();
strengthSlider.style.position = 'absolute';
strengthSlider.style.top = '20px';
strengthSlider.style.left = '20px';
const whiteLevelSlider = document.createElement('input');
whiteLevelSlider.type = 'range';
whiteLevelSlider.min = '0';
whiteLevelSlider.max = '1';
whiteLevelSlider.step = '0.1';
whiteLevelSlider.value = image.embossWhiteLevel();
whiteLevelSlider.style.position = 'absolute';
whiteLevelSlider.style.top = '45px';
whiteLevelSlider.style.left = '20px';
const blendSlider = document.createElement('input');
blendSlider.type = 'range';
blendSlider.min = '0';
blendSlider.max = '1';
blendSlider.step = '0.1';
blendSlider.value = image.embossBlend();
blendSlider.style.position = 'absolute';
blendSlider.style.top = '70px';
blendSlider.style.left = '20px';
// add labels
const strengthLabel = document.createElement('div');
strengthLabel.textContent = 'Strength';
strengthLabel.style.position = 'absolute';
strengthLabel.style.top = '20px';
strengthLabel.style.left = '200px';
const whiteLevelLabel = document.createElement('div');
whiteLevelLabel.textContent = 'White Level';
whiteLevelLabel.style.position = 'absolute';
whiteLevelLabel.style.top = '45px';
whiteLevelLabel.style.left = '200px';
const blendLabel = document.createElement('div');
blendLabel.textContent = 'Blend';
blendLabel.style.position = 'absolute';
blendLabel.style.top = '70px';
blendLabel.style.left = '200px';
// add event listeners
strengthSlider.addEventListener('input', (e) => {
image.embossStrength(parseFloat(e.target.value));
});
whiteLevelSlider.addEventListener('input', (e) => {
image.embossWhiteLevel(parseFloat(e.target.value));
});
blendSlider.addEventListener('input', (e) => {
image.embossBlend(parseFloat(e.target.value));
});
// add elements to the page
document.body.appendChild(strengthSlider);
document.body.appendChild(whiteLevelSlider);
document.body.appendChild(blendSlider);
document.body.appendChild(strengthLabel);
document.body.appendChild(whiteLevelLabel);
document.body.appendChild(blendLabel);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [strength, setStrength] = useState(0.5);
const [whiteLevel, setWhiteLevel] = useState(0.5);
const [blend, setBlend] = useState(0.5);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Emboss]}
embossStrength={strength}
embossWhiteLevel={whiteLevel}
embossDirection="top-left"
embossBlend={blend}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像增强滤镜教程
> 学习如何使用 Konva.js Enhance 滤镜增强 HTML5 Canvas 图像,并调整增强程度。
Source: https://konvajs.org/zh-Hans/docs/filters/Enhance.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 增强图像,可以使用 `Konva.Filters.Enhance` 滤镜
并通过 `enhance` 属性设置增强程度。
**操作说明**:滑动控件以调整增强值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Enhance]);
image.enhance(0.4);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '-1';
slider.max = '1';
slider.step = '0.1';
slider.value = image.enhance();
slider.style.position = 'absolute';
slider.style.top = '20px';
slider.style.left = '20px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image.enhance(value);
});
document.body.appendChild(slider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [enhance, setEnhance] = useState(0.4);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Enhance]}
enhance={enhance}
/>
setEnhance(parseFloat(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像灰度滤镜教程
> 学习如何使用 Konva.js Grayscale 滤镜,将 HTML5 Canvas 图像转换为灰度图像。
Source: https://konvajs.org/zh-Hans/docs/filters/Grayscale.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()` 函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 反转图像颜色,可以使用
`Konva.Filters.Grayscale` 滤镜。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Grayscale]);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image) {
imageRef.current.cache();
}
}, [image]);
return (
{image && (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Grayscale]}
/>
)}
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像色相、饱和度和亮度滤镜教程
> 学习如何使用 Konva.js HSL 滤镜调整 HTML5 Canvas 图像的色相、饱和度和亮度。
Source: https://konvajs.org/zh-Hans/docs/filters/HSL.html
要对 `Konva.Node` 应用滤镜,必须先使用 `cache()` 函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 更改图像的色相、饱和度和亮度分量,可以使用 `Konva.Filters.HSL`。
**操作说明**:滑动控件以更改 HSL 值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.HSL]);
// create sliders
const createSlider = (label, min, max, defaultValue, property) => {
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = `${label}: `;
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.step = '0.1';
slider.value = defaultValue;
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image[property](value);
});
container.appendChild(slider);
return container;
};
const hueSlider = createSlider('Hue', -180, 180, 0, 'hue');
hueSlider.style.top = '20px';
document.body.appendChild(hueSlider);
const saturationSlider = createSlider('Saturation', -2, 10, 0, 'saturation');
saturationSlider.style.top = '45px';
document.body.appendChild(saturationSlider);
const luminanceSlider = createSlider('Luminance', -2, 2, 0, 'luminance');
luminanceSlider.style.top = '70px';
document.body.appendChild(luminanceSlider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [hue, setHue] = useState(0);
const [saturation, setSaturation] = useState(0);
const [luminance, setLuminance] = useState(0);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.HSL]}
hue={hue}
saturation={saturation}
luminance={luminance}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像色相、饱和度和明度滤镜教程
> 学习如何使用 Konva.js HSV 滤镜调整 HTML5 Canvas 图像的色相、饱和度和明度。
Source: https://konvajs.org/zh-Hans/docs/filters/HSV.html
要对 `Konva.Node` 应用滤镜,必须先使用 `cache()` 函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 更改图像的色相、饱和度和明度分量,可以使用 `Konva.Filters.HSV`。
**操作说明**:滑动控件以更改 HSV 值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.HSV]);
// create sliders
const createSlider = (label, min, max, defaultValue, property) => {
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = `${label}: `;
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.step = '0.1';
slider.value = defaultValue;
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image[property](value);
});
container.appendChild(slider);
return container;
};
const hueSlider = createSlider('Hue', -180, 180, 0, 'hue');
hueSlider.style.top = '20px';
document.body.appendChild(hueSlider);
const saturationSlider = createSlider('Saturation', -2, 10, 0, 'saturation');
saturationSlider.style.top = '45px';
document.body.appendChild(saturationSlider);
const value = createSlider('Value', -2, 2, 0, 'value');
value.style.top = '70px';
document.body.appendChild(value);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [hue, setHue] = useState(0);
const [saturation, setSaturation] = useState(0);
const [value, setValue] = useState(0);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.HSV]}
hue={hue}
saturation={saturation}
value={value}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像反色滤镜教程
> 学习如何使用 Konva.js Invert 滤镜反转 HTML5 Canvas 图像的颜色,以产生照片负片效果。
Source: https://konvajs.org/zh-Hans/docs/filters/Invert.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()` 函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 反转图像颜色,可以使用
`Konva.Filters.Invert` 滤镜。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Invert]);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useRef, useEffect } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
{image && (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Invert]}
/>
)}
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像万花筒滤镜教程
> 学习如何使用 Konva.js Kaleidoscope 滤镜为图像创建万花筒效果,并调整强度和角度。
Source: https://konvajs.org/zh-Hans/docs/filters/Kaleidoscope.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 创建万花筒效果,可以使用 `Konva.Filters.Kaleidoscope`
滤镜,并设置 `kaleidoscopePower` 和 `kaleidoscopeAngle` 属性。
**操作说明**:滑动控件以调整万花筒的强度和角度。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Kaleidoscope]);
image.kaleidoscopePower(3);
image.kaleidoscopeAngle(0);
// create sliders
const createSlider = (label, min, max, defaultValue, property) => {
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = `${label}: `;
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.step = property === 'kaleidoscopePower' ? '1' : '0.1';
slider.value = defaultValue;
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image[property](value);
});
container.appendChild(slider);
return container;
};
const powerSlider = createSlider('Power', 2, 8, 3, 'kaleidoscopePower');
powerSlider.style.top = '20px';
document.body.appendChild(powerSlider);
const angleSlider = createSlider('Angle', 0, 360, 0, 'kaleidoscopeAngle');
angleSlider.style.top = '45px';
document.body.appendChild(angleSlider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [angle, setAngle] = useState(0);
const [power, setPower] = useState(3);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Kaleidoscope]}
kaleidoscopePower={power}
kaleidoscopeAngle={angle}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像遮罩滤镜教程
> 学习如何使用 Konva.js Mask 滤镜移除 HTML5 Canvas 图像背景,并调整阈值。
Source: https://konvajs.org/zh-Hans/docs/filters/Mask.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()` 函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 对图像颜色应用遮罩,可以使用
`Konva.Filters.Mask` 滤镜。
`Konva.Filters.Mask` 滤镜会尝试移除图像背景。工作过程如下:
1. 对图像四个角的颜色进行采样。
2. 如果角落颜色相似(差异在 `threshold` 范围内),则假定此颜色代表背景。
3. 然后创建遮罩。与已识别背景颜色相似的像素变为透明,其他像素保持不透明。
4. 使用图像处理技术(例如腐蚀和膨胀)优化遮罩,以去除噪点并平滑边缘。
5. 最后,将优化后的遮罩应用于图像的 Alpha 通道。
`threshold` 属性的范围是 0 到 255。它控制像素颜色必须多接近背景颜色才会被遮罩。较低的阈值只移除非常接近背景的颜色,较高的阈值会移除更大范围的颜色。
**操作说明**:滑动控件以调整遮罩阈值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Mask]);
image.threshold(10);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '255';
slider.value = image.threshold();
slider.style.position = 'absolute';
slider.style.top = '20px';
slider.style.left = '20px';
slider.addEventListener('input', (e) => {
const value = parseInt(e.target.value);
image.threshold(value);
});
document.body.appendChild(slider);
};
imageObj.src = 'https://konvajs.org/assets/space.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [threshold, setThreshold] = useState(10);
const [image] = useImage('https://konvajs.org/assets/space.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Mask]}
threshold={threshold}
/>
setThreshold(parseInt(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 多个滤镜教程
> 学习如何使用 Konva.js 同时对图像应用模糊、亮度和对比度等多个滤镜。
Source: https://konvajs.org/zh-Hans/docs/filters/Multiple_Filters.html
要对 `Konva.Image` 应用多个滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
**操作说明**:使用复选框切换不同滤镜的启用状态,并使用滑块调整滤镜值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
// Create controls container
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '20px';
container.style.left = '20px';
document.body.appendChild(container);
// Filter states
const filterStates = {
blur: false,
brightness: false,
contrast: false,
};
const filterValues = {
blur: 10,
brightness: 1.3,
contrast: 50,
};
// Create filter controls
const createFilterControl = (name, min, max, step, defaultValue) => {
const div = document.createElement('div');
div.style.marginBottom = '10px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = name;
checkbox.checked = filterStates[name];
const label = document.createElement('label');
label.htmlFor = name;
label.textContent = ` ${name.charAt(0).toUpperCase() + name.slice(1)}: `;
const slider = document.createElement('input');
slider.type = 'range';
slider.min = min;
slider.max = max;
slider.step = step;
slider.value = defaultValue;
slider.style.width = '200px';
slider.disabled = !filterStates[name];
div.appendChild(checkbox);
div.appendChild(label);
div.appendChild(slider);
checkbox.addEventListener('change', (e) => {
filterStates[name] = e.target.checked;
slider.disabled = !e.target.checked;
updateFilters();
});
slider.addEventListener('input', (e) => {
filterValues[name] = parseFloat(e.target.value);
updateFilters();
});
return div;
};
// Add controls
container.appendChild(createFilterControl('blur', 0, 40, 1, filterValues.blur));
container.appendChild(createFilterControl('brightness', 0, 2, 0.1, filterValues.brightness));
container.appendChild(createFilterControl('contrast', -100, 100, 1, filterValues.contrast));
function updateFilters() {
const activeFilters = [];
if (filterStates.blur) {
activeFilters.push(Konva.Filters.Blur);
image.blurRadius(filterValues.blur);
}
if (filterStates.brightness) {
activeFilters.push(Konva.Filters.Brightness);
image.brightness(filterValues.brightness);
}
if (filterStates.contrast) {
activeFilters.push(Konva.Filters.Contrast);
image.contrast(filterValues.contrast);
}
image.filters(activeFilters);
}
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const FilterControl = ({ name, min, max, step, filters, setFilters }) => {
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
return (
{
setFilters({
...filters,
[name]: { ...filters[name], active: e.target.checked },
});
}}
/>
{
setFilters({
...filters,
[name]: { ...filters[name], value: parseFloat(e.target.value) },
});
}}
style={{ width: '200px' }}
/>
);
};
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [filters, setFilters] = useState({
blur: { active: false, value: 10 },
brightness: { active: false, value: 1.3 },
contrast: { active: false, value: 50 },
});
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
const activeFilters = [];
if (filters.blur.active) activeFilters.push(Konva.Filters.Blur);
if (filters.brightness.active) activeFilters.push(Konva.Filters.Brightness);
if (filters.contrast.active) activeFilters.push(Konva.Filters.Contrast);
return (
<>
{image && (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={activeFilters}
blurRadius={filters.blur.value}
brightness={filters.brightness.value}
contrast={filters.contrast.value}
/>
)}
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像噪点滤镜教程
> 学习如何使用 Konva.js Noise 滤镜为 HTML5 Canvas 图像添加噪点效果,并调整噪点强度。
Source: https://konvajs.org/zh-Hans/docs/filters/Noise.html
要对 `Konva.Node` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 更改图像噪点,可以使用 `Konva.Filters.Noise`。
**操作说明**:滑动控件以更改噪点值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Noise]);
image.noise(0.3);
// create slider
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '20px';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = 'Noise: ';
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '1';
slider.step = '0.1';
slider.value = image.noise();
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image.noise(value);
});
container.appendChild(slider);
document.body.appendChild(container);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [noise, setNoise] = useState(0.3);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Noise]}
noise={noise}
/>
setNoise(parseFloat(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像像素化滤镜教程
> 学习如何使用 Konva.js Pixelate 滤镜对 HTML5 Canvas 图像进行像素化处理,并调整像素大小。
Source: https://konvajs.org/zh-Hans/docs/filters/Pixelate.html
要对 `Konva.Node` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 更改图像的像素化效果,可以使用 `Konva.Filters.Pixelate`。
**操作说明**:滑动控件以更改像素大小。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Pixelate]);
image.pixelSize(8);
// create slider
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '20px';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = 'Pixel Size: ';
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '2';
slider.max = '32';
slider.step = '1';
slider.value = image.pixelSize();
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseInt(e.target.value);
image.pixelSize(value);
});
container.appendChild(slider);
document.body.appendChild(container);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [pixelSize, setPixelSize] = useState(8);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Pixelate]}
pixelSize={pixelSize}
/>
setPixelSize(parseInt(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像 RGB 滤镜教程
> 学习如何使用 Konva.js RGB 滤镜调整 HTML5 Canvas 图像的红色、绿色和蓝色通道。
Source: https://konvajs.org/zh-Hans/docs/filters/RGB.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 更改图像的 RGB 分量,可以使用 `Konva.Filters.RGB`。
**操作说明**:滑动控件以更改 RGB 值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.RGB]);
image.red(100);
image.green(100);
image.blue(100);
// create sliders
const createSlider = (label, property) => {
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = `${label}: `;
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '255';
slider.step = '1';
slider.value = image[property]();
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseInt(e.target.value);
image[property](value);
});
container.appendChild(slider);
return container;
};
const redSlider = createSlider('Red', 'red');
redSlider.style.top = '20px';
document.body.appendChild(redSlider);
const greenSlider = createSlider('Green', 'green');
greenSlider.style.top = '45px';
document.body.appendChild(greenSlider);
const blueSlider = createSlider('Blue', 'blue');
blueSlider.style.top = '70px';
document.body.appendChild(blueSlider);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [red, setRed] = useState(100);
const [green, setGreen] = useState(100);
const [blue, setBlue] = useState(100);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.RGB]}
red={red}
green={green}
blue={blue}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像棕褐色滤镜教程
> 学习如何使用 Konva.js Sepia 滤镜为 HTML5 Canvas 图像应用棕褐色调效果。
Source: https://konvajs.org/zh-Hans/docs/filters/Sepia.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 为图像应用棕褐色效果,可以使用 `Konva.Filters.Sepia`。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
// Apply Sepia filter
image.cache();
image.filters([Konva.Filters.Sepia]);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useRef, useEffect } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
{image && (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Sepia]}
/>
)}
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像曝光过度滤镜教程
> 学习如何使用 Konva.js Solarize 滤镜为 HTML5 Canvas 图像应用曝光过度效果,并调整阈值。
Source: https://konvajs.org/zh-Hans/docs/filters/Solarize.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 为图像应用曝光过度效果,可以使用 `Konva.Filters.Solarize`。
**操作说明**:滑动控件以更改阈值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Solarize]);
image.threshold(0.5);
// create slider
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '20px';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = 'Threshold: ';
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '1';
slider.step = '0.1';
slider.value = image.threshold();
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image.threshold(value);
});
container.appendChild(slider);
document.body.appendChild(container);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [threshold, setThreshold] = useState(0.5);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Solarize]}
threshold={threshold}
/>
setThreshold(parseFloat(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# HTML5 Canvas 图像阈值滤镜教程
> 学习如何使用 Konva.js Threshold 滤镜将图像转换为黑白图像,并调整阈值。
Source: https://konvajs.org/zh-Hans/docs/filters/Threshold.html
要对 `Konva.Image` 应用滤镜,必须先使用 `cache()`
函数缓存它。然后使用 `filters()` 函数应用滤镜。
要使用 Konva 为图像应用阈值效果,可以使用 `Konva.Filters.Threshold`。
阈值滤镜将图像转换为黑白图像。高于阈值的所有像素会变为白色,低于阈值的所有像素会变为黑色。
**操作说明**:滑动控件以更改阈值。
有关全部可用滤镜,请参阅 [滤镜文档](/zh-Hans/api/Konva.Filters.html)。
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
const imageObj = new Image();
imageObj.onload = () => {
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
draggable: true,
});
layer.add(image);
image.cache();
image.filters([Konva.Filters.Threshold]);
image.threshold(0.5);
// create slider
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.top = '20px';
container.style.left = '20px';
const text = document.createElement('span');
text.textContent = 'Threshold: ';
container.appendChild(text);
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '0';
slider.max = '1';
slider.step = '0.1';
slider.value = image.threshold();
slider.style.width = '200px';
slider.addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
image.threshold(value);
});
container.appendChild(slider);
document.body.appendChild(container);
};
imageObj.src = 'https://konvajs.org/assets/darth-vader.jpg';
imageObj.crossOrigin = 'anonymous';
```
```js
import Konva from 'konva';
import { Stage, Layer, Image } from 'react-konva';
import { useState, useEffect, useRef } from 'react';
import useImage from 'use-image';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [threshold, setThreshold] = useState(0.5);
const [image] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const imageRef = useRef(null);
useEffect(() => {
if (image && imageRef.current) {
imageRef.current.cache();
}
}, [image]);
return (
<>
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
filters={[Konva.Filters.Threshold]}
threshold={threshold}
/>
setThreshold(parseFloat(e.target.value))}
style={{ position: 'absolute', top: '20px', left: '20px' }}
/>
>
);
};
export default App;
```
```js
```
---
# 将图形移动到其他容器
> 学习如何在 Konva 中使用 moveTo() 方法,在组、图层和容器之间移动图形。
Source: https://konvajs.org/zh-Hans/docs/groups_and_layers/Change_Containers.html
要使用 Konva 将图形从一个容器移动到另一个容器,可以使用 `moveTo()` 方法。此方法需要一个容器作为参数。
容器可以是另一个舞台、图层或组。你也可以将组移动到其他组或图层中,或将组中的图形直接移动到其他图层中。
**操作说明:拖放这些组,观察红色矩形绑定到黄色组还是蓝色组。使用左侧按钮将矩形从一个组移动到另一个组。**
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
// yellow group
const group1 = new Konva.Group({
x: 50,
y: 50,
draggable: true,
});
const yellow = new Konva.Rect({
width: 100,
height: 100,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
});
group1.add(yellow);
// blue group
const group2 = new Konva.Group({
x: 200,
y: 50,
draggable: true,
});
const blue = new Konva.Rect({
width: 100,
height: 100,
fill: 'blue',
stroke: 'black',
strokeWidth: 4,
});
group2.add(blue);
// red box
const redBox = new Konva.Rect({
x: 10,
y: 10,
width: 30,
height: 30,
fill: 'red',
});
group1.add(redBox);
layer.add(group1);
layer.add(group2);
stage.add(layer);
// create buttons
const moveToGroup1Btn = document.createElement('button');
moveToGroup1Btn.textContent = 'Move to yellow group';
moveToGroup1Btn.addEventListener('click', () => {
redBox.moveTo(group1);
});
const moveToGroup2Btn = document.createElement('button');
moveToGroup2Btn.textContent = 'Move to blue group';
moveToGroup2Btn.addEventListener('click', () => {
redBox.moveTo(group2);
});
document.body.appendChild(moveToGroup1Btn);
document.body.appendChild(moveToGroup2Btn);
````
```js
import { Stage, Layer, Rect, Group } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [redBoxGroup, setRedBoxGroup] = useState('yellow');
const [groupPositions, setGroupPositions] = useState({
yellow: { x: 50, y: 50 },
blue: { x: 200, y: 50 },
});
const handleDragEnd = (group, e) => {
setGroupPositions((positions) => ({
...positions,
[group]: { x: e.target.x(), y: e.target.y() },
}));
};
return (
<>
handleDragEnd('yellow', e)}
>
{redBoxGroup === 'yellow' && (
)}
handleDragEnd('blue', e)}
>
{redBoxGroup === 'blue' && (
)}
>
);
};
export default App;
````
```js
```
---
# 图形组
> 学习如何使用 Konva.Group 将多个图形组合在一起,并将它们作为一个整体移动、旋转和缩放。
Source: https://konvajs.org/zh-Hans/docs/groups_and_layers/Groups.html
要使用 Konva 将多个图形组合在一起,可以实例化一个 `Konva.Group()` 对象,然后使用 `add()` 方法向其中添加图形。
如果要同时移动、旋转或缩放多个图形,将这些图形组合在一起非常方便。
还可以将组添加到其他组中,以创建更复杂的节点树。
有关属性和方法的完整列表,请参阅 [Konva.Group 文档](/zh-Hans/api/Konva.Group.html)。如果要将不透明度或混合模式作为一个整体应用于组,请参阅[隔离组](/zh-Hans/docs/groups_and_layers/Isolated_Groups.html)。
**操作说明:尝试拖动组。观察所有图形如何一起移动。**
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
const group = new Konva.Group({
x: 50,
y: 50,
draggable: true,
});
const circle = new Konva.Circle({
x: 40,
y: 40,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
});
const rect = new Konva.Rect({
x: 80,
y: 20,
width: 100,
height: 50,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
});
group.add(circle);
group.add(rect);
layer.add(group);
stage.add(layer);
````
```js
import { Stage, Layer, Group, Circle, Rect } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
return (
{
setPosition({ x: e.target.x(), y: e.target.y() });
}}
>
);
};
export default App;
````
```js
```
---
# HTML5 Canvas 隔离组:组不透明度与混合模式
> 使用 Konva.Group 的 isolated 属性,将不透明度和混合模式一次性应用于整组图形,并将子节点的混合模式和擦除效果限制在组内。
Source: https://konvajs.org/zh-Hans/docs/groups_and_layers/Isolated_Groups.html
默认情况下,`Konva.Group` 只负责组织其子节点。绘制组时,每个子节点都会直接绘制到图层 Canvas 上:
- 组的 `opacity` 会乘到每个子节点上,因此你可以看到子节点的重叠区域。
- 设置了 `globalCompositeOperation`(例如 `multiply` 或 `destination-out`)的子节点,会与图层上已绘制的所有内容混合,而不仅仅是与其兄弟节点混合。
设置 `isolated: true` 可以将组作为一个图像绘制。隔离组会先将子节点绘制到一个单独的透明 Canvas 上,然后一步将结果放到图层上,并只应用一次自身的 `opacity` 和 `globalCompositeOperation`。其效果类似于 CSS 中的 `isolation: isolate` 以及 SVG `` 上的不透明度。
```javascript
const group = new Konva.Group({
isolated: true,
opacity: 0.6,
});
```
启用隔离后:
- **组的不透明度应用于整个图像。** 重叠的子节点不会互相透出。
- **组的混合模式应用于整个图像。** `group.globalCompositeOperation('multiply')` 会将合成后的组与其下方的内容混合。
- **子节点的混合模式限制在组内。** 设置了 `destination-out` 的子节点只会擦除组自身的内容,而不会擦除图层的其余部分。
隔离功能需要 Konva 10.7.0 或更高版本。有关完整的属性列表,请参阅 [Konva.Group 文档](/zh-Hans/api/Konva.Group.html)。
**操作说明:将两个组拖到条纹背景上。左侧是普通组:其图形会互相透出,设置了 `destination-out` 的黑色圆形还会部分擦除条纹。右侧是隔离组:它作为一个整体图像变透明,孔洞中显示的是其后面的条纹。**
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// stripes, to see what the groups let through
for (let x = 0; x < stage.width(); x += 40) {
layer.add(
new Konva.Rect({
x: x,
width: 20,
height: stage.height(),
fill: '#8ecae6',
})
);
}
function createGroup(x, isolated) {
const group = new Konva.Group({
x: x,
y: 40,
opacity: 0.6,
isolated: isolated,
draggable: true,
});
group.add(
new Konva.Rect({
width: 120,
height: 120,
fill: '#e63946',
})
);
group.add(
new Konva.Circle({
x: 120,
y: 120,
radius: 70,
fill: '#1d3557',
})
);
// erases everything under it: the whole layer, or only the isolated group
group.add(
new Konva.Circle({
x: 60,
y: 60,
radius: 30,
fill: 'black',
globalCompositeOperation: 'destination-out',
})
);
group.add(
new Konva.Text({
y: 205,
text: isolated ? 'isolated: true' : 'default group',
fontSize: 18,
})
);
layer.add(group);
}
createGroup(40, false);
createGroup(300, true);
```
```js
import { Stage, Layer, Group, Rect, Circle, Text } from 'react-konva';
import { useState } from 'react';
const width = window.innerWidth;
const height = window.innerHeight;
const stripes = Array.from({ length: Math.ceil(width / 40) }, (_, i) => i * 40);
const App = () => {
const [groups, setGroups] = useState([
{ id: 'default', x: 40, y: 40, isolated: false },
{ id: 'isolated', x: 300, y: 40, isolated: true },
]);
const handleDragEnd = (id, e) => {
const { x, y } = e.target.position();
setGroups((current) =>
current.map((group) => (group.id === id ? { ...group, x, y } : group))
);
};
return (
{/* stripes, to see what the groups let through */}
{stripes.map((x) => (
))}
{groups.map((group) => (
handleDragEnd(group.id, e)}
>
{/* erases everything under it: the whole layer, or only the isolated group */}
))}
);
};
export default App;
```
```js
```
## 隔离还是缓存?
[`group.cache()`](/zh-Hans/docs/performance/Shape_Caching.html) 也会将组作为一个图像绘制,但它生成的是快照。更改子节点后,必须再次调用 `cache()`。隔离组则保持实时更新:更改、动画化或拖动任意子节点,下一次绘制就会显示结果。
代价是每次绘制图层时都要经过一个缓冲 Canvas。Konva 会让该缓冲区只与组的可见部分一样大,在多个隔离组之间复用它,并在不再使用时将其释放。对于会变化的内容,请使用隔离。对于很少变化的复杂组,或者需要使用[滤镜](/zh-Hans/docs/filters/Blur.html)时,请使用 `cache()`。
## 注意事项
- Konva 根据组的边界确定缓冲区的大小。如果[自定义图形](/zh-Hans/docs/shapes/Custom.html)在其 `width` 和 `height` 之外绘制内容,请使用 `selfRectFunc` 描述绘制区域,否则这部分内容可能会被裁剪。
- 隔离只改变组的外观。命中检测保持不变:被兄弟节点擦除的图形仍然会接收事件。
- 缓存组会保留其快照。如果对隔离组调用 `cache()`,在调用 `clearCache()` 之前,将一直绘制该快照。
---
# 图形层叠
> 学习如何在 Konva 中使用 moveToTop、moveToBottom、moveUp 和 moveDown 方法调整 Canvas 上图形的顺序。
Source: https://konvajs.org/zh-Hans/docs/groups_and_layers/Layering.html
要使用 Konva 设置图形的层叠顺序,可以使用以下任一层叠方法:
`moveToTop()`、`moveToBottom()`、`moveUp()`、`moveDown()` 或 `zIndex()`。
你也可以设置组和图层的层叠顺序。
**操作说明:拖放矩形以移动它们,然后使用左侧按钮调整黄色矩形的顺序。**
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
const yellowBox = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
draggable: true,
});
const redBox = new Konva.Rect({
x: 100,
y: 100,
width: 100,
height: 100,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
draggable: true,
});
layer.add(yellowBox);
layer.add(redBox);
stage.add(layer);
// create buttons
const toTopBtn = document.createElement('button');
toTopBtn.textContent = 'Move yellow box to top';
toTopBtn.addEventListener('click', () => {
yellowBox.moveToTop();
});
const toBottomBtn = document.createElement('button');
toBottomBtn.textContent = 'Move yellow box to bottom';
toBottomBtn.addEventListener('click', () => {
yellowBox.moveToBottom();
});
document.body.prepend(toTopBtn);
document.body.prepend(toBottomBtn);
````
```js
import { Stage, Layer, Rect } from 'react-konva';
import { useRef, useState } from 'react';
const App = () => {
const yellowRef = useRef();
const [positions, setPositions] = useState({
yellow: { x: 50, y: 50 },
red: { x: 100, y: 100 },
});
const handleDragEnd = (color, e) => {
setPositions((currentPositions) => ({
...currentPositions,
[color]: { x: e.target.x(), y: e.target.y() },
}));
};
return (
<>
handleDragEnd('yellow', e)}
/>
handleDragEnd('red', e)}
/>
>
);
};
export default App;
````
```js
```
---
# 理解节点的 zIndex
> 了解 zIndex 在 Konva 中如何表示节点在其父节点 children 数组中的索引,以及它与 CSS z-index 的区别。
Source: https://konvajs.org/zh-Hans/docs/groups_and_layers/zIndex.html
## 节点的 zIndex 是什么?
可以通过以下方式获取或设置节点的 zIndex:
```javascript
// get
const zIndex = shape.zIndex();
// set
shape.zIndex(1);
```
zIndex 只是节点在其父节点 children 数组中的索引。不要混淆 Konva 中的 `zIndex` 和 CSS 中的 `z-index`。
```javascript
const group = new Konva.Group();
const circle = new Konva.Circle({});
group.add(circle);
// it will log 0
console.log(circle.zIndex());
// the next line will not work because the group has only one child
circle.zIndex(1);
// still logs 0
console.log(circle.zIndex());
// for any node this equation will be true:
console.log(circle.zIndex() === circle.getParent().children.indexOf(circle));
```
不能像在 CSS 中那样使用 `zIndex` 设置节点的绝对位置。
Konva 严格按照节点在节点树中的定义顺序绘制节点。
**操作说明:尝试使用按钮更改图形的 zIndex。观察图形顺序如何变化。**
```js
import Konva from 'konva';
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
// first group
const group1 = new Konva.Group();
layer.add(group1);
const blackRect = new Konva.Rect({
x: 10,
y: 10,
width: 100,
height: 100,
fill: 'black',
});
group1.add(blackRect);
const redCircle = new Konva.Circle({
x: 80,
y: 80,
radius: 40,
fill: 'red',
});
group1.add(redCircle);
// second group
const group2 = new Konva.Group();
layer.add(group2);
const greenRect = new Konva.Rect({
x: 50,
y: 50,
width: 100,
height: 100,
fill: 'green',
});
group2.add(greenRect);
stage.add(layer);
// create buttons
const btn1 = document.createElement('button');
btn1.textContent = 'Move red circle to group2';
btn1.addEventListener('click', () => {
redCircle.moveTo(group2);
});
const btn2 = document.createElement('button');
btn2.textContent = 'Move red circle to group1';
btn2.addEventListener('click', () => {
redCircle.moveTo(group1);
});
document.body.appendChild(btn1);
document.body.appendChild(btn2);
````
```js
import { Stage, Layer, Group, Rect, Circle } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [redCircleGroup, setRedCircleGroup] = useState('group1');
return (
<>
{redCircleGroup === 'group1' && (
)}
{redCircleGroup === 'group2' && (
)}
>
);
};
export default App;
````
```js
```
---
# HTML5 Canvas 避免内存泄漏技巧
> 使用 Konva.js 防止 HTML5 Canvas 应用发生内存泄漏。学习如何正确销毁图形、移除事件并管理 Canvas 资源。
Source: https://konvajs.org/zh-Hans/docs/performance/Avoid_Memory_Leaks.html
### 删除图形
`remove()` 和 `destroy()` 是两个非常相似的方法。如果需要彻底删除节点,请使用 `destroy()`。`destroy()` 方法从 KonvaJS 引擎中删除对该节点的所有引用。如果要重复使用节点,请使用 `remove()`,之后可以再次将该节点添加到任何容器。
### 补间
使用 `Konva.Tween` 实例时,必须在使用后将其销毁。
以下示例展示了正确的内存管理:
```js
import Konva from 'konva';
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight,
});
const layer = new Konva.Layer();
stage.add(layer);
// Create circle
const circle = new Konva.Circle({
x: 100,
y: 100,
radius: 30,
fill: 'red'
});
layer.add(circle);
// Add buttons
const addButton = document.createElement('button');
addButton.textContent = 'Add Circle';
document.body.appendChild(addButton);
const removeButton = document.createElement('button');
removeButton.textContent = 'Remove Circle';
document.body.appendChild(removeButton);
const animateButton = document.createElement('button');
animateButton.textContent = 'Animate';
document.body.appendChild(animateButton);
// Handle adding/removing
addButton.addEventListener('click', () => {
layer.add(circle);
});
removeButton.addEventListener('click', () => {
// Just remove from layer, can be added back
circle.remove();
});
animateButton.addEventListener('click', () => {
// Using to() method which auto-destroys the tween
circle.to({
x: Math.random() * stage.width(),
y: Math.random() * stage.height(),
duration: 1
});
// If using Tween directly, make sure to destroy it
const tween = new Konva.Tween({
node: circle,
rotation: 360,
duration: 1,
onFinish: function() {
// Clean up the tween
tween.destroy();
}
}).play();
});
```
```js
import { Stage, Layer, Circle } from 'react-konva';
import { useState } from 'react';
const App = () => {
const [isVisible, setIsVisible] = useState(true);
const [position, setPosition] = useState({ x: 100, y: 100 });
const [rotation, setRotation] = useState(0);
const handleAdd = () => {
setIsVisible(true);
};
const handleRemove = () => {
setIsVisible(false);
};
const handleAnimate = () => {
// Update position
setPosition({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight
});
// Update rotation
setRotation(rotation + 360);
};
return (
{isVisible && (
)}
);
};
export default App;
```
```js
```
---
# 自动重绘——你需要调用 draw() 或 batchDraw() 吗?
> 自 Konva 8 起重绘是自动的,因此在修改图形之后不再需要 layer.draw() 和 layer.batchDraw()。本文说明何时仍需手动重绘,以及如何关闭批量绘制。
Source: https://konvajs.org/zh-Hans/docs/performance/Batch_Draw.html
**简短回答:不需要。自 Konva 8 起,修改图形之后不需要调用 `draw()` 或 `batchDraw()`。**
```js
// This is enough. Konva schedules the redraw itself.
rect.fill('red');
rect.x(120);
// Both of these are redundant.
layer.draw();
layer.batchDraw();
```
设置任何属性都会把图层标记为需要重绘,Konva 会在下一个动画帧中重绘一次。连续修改十个属性
仍然只会产生一次重绘,因此你自己调用 `batchDraw()` 不会带来任何收益——批量处理已经发生了。
你仍然会在较旧的教程、2021 年之前写的回答,以及 AI 助手生成的代码中看到 `layer.draw()`,
因为语料库里到处都是它。它是无害的,只是没有必要。删掉这些调用不会改变屏幕上的显示结果。
## 什么时候仍然需要手动重绘
自动重绘响应的是 *Konva* 的变化。如果变化发生在 Konva 的感知之外,就没有任何东西会把图层
标记为需要重绘,你必须自己说明。
常见情况是 `Konva.Image` 背后的源会自行变化——`` 只是用于 Portal 类 API 的包装器。如果尝试将 Canvas 导出为图像,HTML 内容将不可见。
```js
import React from 'react';
import { Stage, Layer, Rect } from 'react-konva';
import { Html } from 'react-konva-utils';
const App = () => {
return (
);
};
export default App;
```
---
# Canvas 图形拖放
> 学习如何在 React 中使用 react-konva 实现 Canvas 拖放。通过 draggable 属性使图形可拖动,并处理拖动事件。
Source: https://konvajs.org/zh-Hans/docs/react/Drag_And_Drop.html
要为 Canvas 上的任何节点启用拖放,只需将 `draggable` 属性传给组件。
拖放图形时,建议将图形位置保存到应用的数据存储中。可以为此使用 `onDragMove` 和 `onDragEnd` 事件。
:::note 拖动边界与 React 状态
Konva 会在拖动时直接改变节点的位置。默认情况下,React-Konva 只会应用与上次渲染相比发生变化的属性。
如果 React 状态中已经是 `y: 50`,再次将状态设置为 `y: 50` 并不会重置节点的位置。
在实现自定义边界或吸附功能时,事件处理函数必须先直接更新节点,再将 `node.position()` 保存到状态中。
[简单拖动边界](/zh-Hans/docs/drag_and_drop/Simple_Drag_Bounds.html)和[复杂拖放](/zh-Hans/docs/drag_and_drop/Complex_Drag_and_Drop.html)示例采用了这种方式。
也可以使用 `dragBoundFunc`,在 Konva 应用每次拖动位置之前限制节点的位置。
:::
```js
import React from 'react';
import { Stage, Layer, Text } from 'react-konva';
const App = () => {
const [isDragging, setIsDragging] = React.useState(false);
const [position, setPosition] = React.useState({
x: 50,
y: 50,
});
return (
{
setIsDragging(true);
}}
onDragEnd={(e) => {
setIsDragging(false);
setPosition({
x: e.target.x(),
y: e.target.y(),
});
}}
/>
);
};
export default App;
```
---
# 如何使用 React 将图像元素拖放到 Canvas 中?
> 学习如何配合 React 使用 HTML5 拖放功能,将页面中的图像拖放到 Konva Canvas 中。
Source: https://konvajs.org/zh-Hans/docs/react/Drop_Image.html
可以使用原生 HTML5 拖放功能,将页面中的图像或其他元素放入 Canvas 区域。
```js
import React from 'react';
import { Stage, Layer, Image } from 'react-konva';
import useImage from 'use-image';
const URLImage = ({ image, onChange }) => {
const [img] = useImage(image.src);
return (
{
onChange({ ...image, ...event.target.position() });
}}
/>
);
};
const App = () => {
const dragUrl = React.useRef();
const stageRef = React.useRef();
const nextImageId = React.useRef(0);
const [images, setImages] = React.useState([]);
return (
Try to drag and drop the image into the stage:

{
dragUrl.current = e.target.src;
}}
/>
{
e.preventDefault();
// register event position
stageRef.current.setPointersPositions(e);
// add image
setImages((currentImages) =>
currentImages.concat([
{
id: nextImageId.current++,
...stageRef.current.getPointerPosition(),
src: dragUrl.current,
},
])
);
}}
onDragOver={(e) => e.preventDefault()}
>
{images.map((image) => {
return (
{
setImages((currentImages) =>
currentImages.map((currentImage) =>
currentImage.id === newAttributes.id
? newAttributes
: currentImage
)
);
}}
/>
);
})}
);
};
export default App;
```
---
# 如何使用 React 和 Konva 监听 Canvas 图形上的事件?
> 学习如何在 React 中使用 react-konva 处理 Canvas 事件。在 Konva 组件上使用 onClick、onMouseEnter、onDragEnd 等事件 props。
Source: https://konvajs.org/zh-Hans/docs/react/Events.html
使用 `react-konva`,可以将 `Konva` 支持的任何事件绑定到 Canvas 节点。
为此,可以使用 `onEventName` 模式。例如,使用 `onMouseDown` 处理 `mousedown`,使用 `onDragEnd` 处理 `dragend`。
有关完整的事件列表,请参阅 [on() 方法文档](/zh-Hans/api/Konva.Node.html)。
本示例使用 `dragstart` 和 `dragend` 事件创建交互式星空:
```js
import React from 'react';
import { Stage, Layer, Star, Text } from 'react-konva';
function generateShapes() {
return [...Array(10)].map((_, i) => ({
id: i.toString(),
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
rotation: Math.random() * 180,
isDragging: false,
}));
}
const App = () => {
const [stars, setStars] = React.useState(generateShapes());
const handleDragStart = (e) => {
const id = e.target.id();
setStars((currentStars) =>
currentStars.map((star) => {
return {
...star,
isDragging: star.id === id,
};
})
);
};
const handleDragEnd = (e) => {
const id = e.target.id();
const position = e.target.position();
setStars((currentStars) =>
currentStars.map((star) => {
return {
...star,
isDragging: false,
...(star.id === id ? position : {}),
};
})
);
};
return (
{stars.map((star) => (
))}
);
};
export default App;
```
---
# 如何使用 React 和 Konva 应用 Canvas 滤镜?
> 学习如何配合缓存使用 react-konva,对图形和图像应用模糊、噪点等 Canvas 滤镜。
Source: https://konvajs.org/zh-Hans/docs/react/Filters.html
要对 Konva 节点应用滤镜,请执行以下操作:
1. 使用 `node.cache()` 缓存节点
2. 使用 `filters` props 应用滤镜
3. 节点属性更改时,重新缓存节点
```js
import React from 'react';
import Konva from 'konva';
import { Stage, Layer, Rect, Image } from 'react-konva';
import useImage from 'use-image';
// Example of functional component with image filter
const FilterImage = () => {
const [image] = useImage('https://konvajs.org/assets/lion.png', 'anonymous');
const imageRef = React.useRef();
// when image is loaded we need to cache the shape
React.useEffect(() => {
if (image) {
// you many need to reapply cache on some props changes like shadow, stroke, etc.
imageRef.current.cache();
}
}, [image]);
return (
);
};
// Example of class component with noise filter
// Try to click on rect to see color updates
const FilterRect = () => {
const [color, setColor] = React.useState('green');
const rectRef = React.useRef();
React.useEffect(() => {
if (rectRef.current) {
rectRef.current.cache();
}
}, []);
const handleClick = () => {
setColor(Konva.Util.getRandomColor());
// recache shape when we updated it
rectRef.current.cache();
};
return (
);
};
const App = () => {
return (
);
};
export default App;
```
---
# 如何使用 React 在 Canvas 上实现自由绘制?
> 学习如何在 React 中使用 react-konva 实现 Canvas 自由绘制(白板)。跟踪鼠标和触摸事件以徒手绘制线条。
Source: https://konvajs.org/zh-Hans/docs/react/Free_Drawing.html
本示例展示如何以“React 方式”实现具有完整矢量表示的自由绘制应用。
这种实现适用于许多白板应用。你可以添加 [撤销/重做功能](/zh-Hans/docs/react/Undo-Redo.html),并将完整 state 保存到后端。
注意:如果 state 中包含太多线条,应用速度会变慢。如果要支持绘制成百上千条线,需要进行额外优化。
本示例展示以下操作:
1. 使用 `React.useRef` 跟踪绘制 state,以提高性能
2. 将线条以矢量数据形式存储在 React state 中
3. 处理绘制所需的鼠标和触摸事件
4. 使用 `globalCompositeOperation` 实现画笔和橡皮擦工具
5. 使用圆角端点和张力创建平滑线条
```js
import React from 'react';
import { Stage, Layer, Line, Text } from 'react-konva';
const App = () => {
const [tool, setTool] = React.useState('pen');
const [lines, setLines] = React.useState([]);
const isDrawing = React.useRef(false);
const handleMouseDown = (e) => {
isDrawing.current = true;
const pos = e.target.getStage().getPointerPosition();
setLines([...lines, { tool, points: [pos.x, pos.y] }]);
};
const handleMouseMove = (e) => {
// no drawing - skipping
if (!isDrawing.current) {
return;
}
const stage = e.target.getStage();
const point = stage.getPointerPosition();
let lastLine = lines[lines.length - 1];
// add point
lastLine.points = lastLine.points.concat([point.x, point.y]);
// replace last
lines.splice(lines.length - 1, 1, lastLine);
setLines(lines.concat());
};
const handleMouseUp = () => {
isDrawing.current = false;
};
return (
{lines.map((line, i) => (
))}
);
};
export default App;
```
---
# React 如何在 Canvas 上绘制图像?
> 学习如何在 React 中使用 react-konva 在 Canvas 上绘制图像。通过 Image 组件和 useImage hook 加载并显示图像。
Source: https://konvajs.org/zh-Hans/docs/react/Images.html
要使用 React 在 Canvas 上渲染图像,可以使用 [use-image](https://github.com/konvajs/use-image) hook。
```js
import React from 'react';
import { Stage, Layer, Image } from 'react-konva';
import useImage from 'use-image';
const URLImage = ({ src, ...rest }) => {
const [image] = useImage(src, 'anonymous');
return ;
};
const App = () => {
return (
);
};
export default App;
```
---
# 使用 React 绘制 Canvas 图形
> 学习如何在 React 中使用 react-konva 在 Canvas 上绘制图形。将 Rect、Circle、Line、Text 等 Konva 组件用作 React 元素。
Source: https://konvajs.org/zh-Hans/docs/react/Shapes.html
所有 `react-konva` 组件都对应同名的 `Konva` 组件。
除非另有说明,`Konva` 对象的所有可用参数都是对应
`react-konva` 组件的有效 props。
核心图形包括:`Rect`、`Circle`、`Ellipse`、`Line`、`Image`、`Text`、`TextPath`、`Star`、
`Label`、`SVG Path`、`RegularPolygon`。你还可以创建 [自定义图形](/zh-Hans/docs/react/Custom_Shape.html)。
下面的示例展示一些使用不同样式选项的基本图形:
```js
import React from 'react';
import { Stage, Layer, Rect, Text, Circle, Line } from 'react-konva';
const App = () => {
return (
);
};
export default App;
```
---
# 如何使用 React 和 Konva 为 Canvas 图形制作动画?
> 学习如何在 React 中使用 react-konva 为 Canvas 图形添加动画。使用 React state、requestAnimationFrame 和 Konva.Tween 创建平滑动画。
Source: https://konvajs.org/zh-Hans/docs/react/Simple_Animations.html
Konva 提供两种动画方法:[补间](/zh-Hans/docs/tweens/Linear_Easing.html)和[动画](/zh-Hans/docs/animations/Rotation.html)。你可以手动将它们应用于节点。
对于简单用例,建议使用 `node.to()` 方法。对于更复杂的动画,请参阅 [复杂的 react-konva 动画示例](/zh-Hans/docs/react/Complex_Animations.html)。
本示例使用 [refs API](/zh-Hans/docs/react/Access_Konva_Nodes.html) 直接访问图形实例。
操作说明:尝试拖动矩形并观察其动画。
```js
import React, { useRef } from 'react';
import { Stage, Layer, Rect } from 'react-konva';
const MyRect = () => {
const rectRef = useRef(null);
const changeSize = () => {
// to() is a method of `Konva.Node` instances
rectRef.current.to({
scaleX: Math.random() + 0.8,
scaleY: Math.random() + 0.8,
duration: 0.2,
});
};
return (
);
};
const App = () => {
return (
);
};
export default App;
```
---
# 如何测试 react-konva 组件
> 在真实浏览器中测试 react-konva 组件,而不是在 jsdom 中——那里的 canvas 是桩实现,测试会通过却什么都没有断言。本文介绍如何获取舞台句柄、基于场景图断言、模拟拖动,以及检查泄漏。
Source: https://konvajs.org/zh-Hans/docs/react/Testing.html
测试 Canvas 与测试 DOM 不同。你的图形不是元素,因此 Testing Library 的查询永远找不到它们,
而且大多数 React 项目所使用的测试环境根本无法绘制。
## 在真实浏览器中运行 Canvas 测试
用 jsdom 测试你的状态存储和逻辑,用真实浏览器测试任何涉及 `Stage` 的部分。
这一点比听起来更重要,因为 **jsdom 不会明确报错**。它没有 canvas 实现,因此常见做法是用一个
桩,让所有上下文调用都返回空值。没有任何异常抛出,测试通过。而你实际断言的是:
| 你测试的内容 | jsdom 给你的结果 |
| --- | --- |
| `stage.getIntersection(pos)` | `null`,无论该点下方是什么 |
| 测量后的 `text.width()` | 来自一个从未加载的字体的数值 |
| `stage.toDataURL()` | 一个桩字符串,而不是图像 |
| 像素读取 | 完全透明 |
只有纯 JavaScript 的部分——`find()`、属性、场景图——行为是正确的。因此一个命中检测的测试会通过,
却根本没有测试命中检测。这正是编程代理无法替你发现的那类失败:它写出测试,测试是绿的,循环就结束了。
`react-konva` 自身就是这样测试的。它的配置很短:
```js
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
export default defineConfig({
test: {
browser: {
enabled: true,
provider: playwright(),
headless: process.env.HEADLESS !== 'false',
instances: [{ browser: 'chromium' }],
},
globals: true,
},
});
```
如果你已有 jsdom 测试,请保留它们。把运行拆成两个项目——一个用 jsdom 测逻辑,一个用浏览器测
Canvas——而不是把所有测试都迁走。
## 获取舞台句柄
使用 `ref` 是最干净的方式:
```jsx
const stageRef = React.useRef(null);
render(…);
stageRef.current.find('#target');
```
当被测组件自己持有舞台且不向你暴露 ref 时,Konva 维护了一个注册表:
```js
import Konva from 'konva';
const stage = Konva.stages[Konva.stages.length - 1];
```
**在 `` 上使用 `data-testid` 是无效的。** 该组件渲染一个容器 `div`,并且只透传
`id`、`accessKey`、`className`、`role`、`style`、`tabIndex` 和 `title`。其他属性(包括
`data-*`)都会被丢弃。如果你想查询容器,请使用 `id`;并且记住容器是一个 `div`——`