HTML5 Canvas 自定义命中检测函数教程
可以使用两种方式更改图形的命中区域:hitFunc 和 hitStrokeWidth 属性。
1. 什么是 hitFunc?
要使用 Konva 为图形创建自定义命中绘制函数,可以设置
hitFunc 属性。Konva 使用命中绘制函数绘制用于命中检测的区域。
自定义命中绘制函数有多种用途。例如,你可以扩大命中区域,
让用户更容易与图形交互。你也可以检测图形的某些部分并忽略其他部分,
或简化命中绘制函数以提高渲染性能。
另请参阅编写自定义 sceneFunc 的最佳实践,这些实践也适用于 hitFunc。
hitFunc 是一个有两个参数的函数:一个 Konva.Context 渲染器和一个图形实例。
2. 什么是 hitStrokeWidth?
对于 Konva.Line 等图形,覆盖 hitFunc 很困难。有时,你只想加粗事件命中区域。此时,最好为 hitStrokeWidth 属性设置较大的值。
操作说明:在星形上触发 mouseover、mouseout、mousedown 和 mouseup。 你会看到命中区域是一个包围图形的大圆。也请对线条执行相同操作。 你还可以切换命中 Canvas 来查看其外观。这对调试很有用。
- Vanilla
- React
- Vue
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);
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 (
<>
<button onClick={() => setShowHit(!showHit)}>Toggle hit canvas</button>
<Stage width={window.innerWidth} height={window.innerHeight}>
<Layer>
<Text x={10} y={10} text={message} fontSize={24} />
<Star
x={window.innerWidth / 4}
y={window.innerHeight / 2}
numPoints={5}
innerRadius={40}
outerRadius={70}
fill="red"
stroke="black"
strokeWidth={4}
hitFunc={(context, shape) => {
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}
/>
<Line
x={window.innerWidth * 0.6}
y={window.innerHeight / 2}
points={[-50, -50, 50, 50]}
stroke="black"
strokeWidth={2}
hitStrokeWidth={20}
onMouseover={handleLineEvent}
onMouseout={handleLineEvent}
onMousedown={handleLineEvent}
onMouseup={handleLineEvent}
/>
</Layer>
</Stage>
</>
);
};
export default App;
<template>
<div>
<button @click="toggleHit">Toggle hit canvas</button>
<v-stage :config="stageSize" ref="stageRef">
<v-layer ref="layerRef">
<v-text :config="textConfig" />
<v-star
:config="starConfig"
@mouseover="handleStarEvent"
@mouseout="handleStarEvent"
@mousedown="handleStarEvent"
@mouseup="handleStarEvent"
/>
<v-line
:config="lineConfig"
@mouseover="handleLineEvent"
@mouseout="handleLineEvent"
@mousedown="handleLineEvent"
@mouseup="handleLineEvent"
/>
</v-layer>
</v-stage>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const message = ref('');
const showHit = ref(false);
const stageRef = ref(null);
const layerRef = ref(null);
const stageSize = {
width: window.innerWidth,
height: window.innerHeight
};
const textConfig = computed(() => ({
x: 10,
y: 10,
text: message.value,
fontSize: 24
}));
const starConfig = {
x: window.innerWidth / 4,
y: window.innerHeight / 2,
numPoints: 5,
innerRadius: 40,
outerRadius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
hitFunc: function(context, shape) {
context.beginPath();
context.arc(0, 0, 70, 0, Math.PI * 2, true);
context.closePath();
context.fillStrokeShape(shape);
}
};
const lineConfig = {
x: window.innerWidth * 0.6,
y: window.innerHeight / 2,
points: [-50, -50, 50, 50],
stroke: 'black',
strokeWidth: 2,
hitStrokeWidth: 20
};
const handleStarEvent = (e) => {
message.value = e.type + ' star';
};
const handleLineEvent = (e) => {
message.value = e.type + ' line';
};
const toggleHit = () => {
showHit.value = !showHit.value;
const stage = stageRef.value.getNode().container();
if (showHit.value) {
const hitCanvas = layerRef.value.getNode().hitCanvas._canvas;
stage.style.border = '2px solid black';
stage.appendChild(hitCanvas);
hitCanvas.style.position = 'absolute';
hitCanvas.style.top = '0';
hitCanvas.style.left = '0';
} else {
const hitCanvas = layerRef.value.getNode().hitCanvas._canvas;
hitCanvas.remove();
}
};
</script>