HTML5 Canvas 自定义命中检测函数教程
改变图形命中区域有两种方式:hitFunc
和 hitStrokeWidth
属性。
1. 什么是 hitFunc
?
要为 Konva 中的图形创建自定义命中绘制函数,可以设置 hitFunc
属性。命中绘制函数是 Konva 用来绘制用于命中检测的区域的函数。使用自定义命中绘制函数有多种好处,比如使命中区域更大,方便用户与图形交互;只让图形的某些部分可以被检测到,其他部分则不行;或者简化命中绘制函数以提升渲染性能。
另外,可以查看一些编写自定义 sceneFunc
的最佳实践,sceneFunc
也可用于 hitFunc
。
hitFunc
是一个带有两个参数的函数:Konva.Context
渲染器和一个图形实例。
2. 什么是 hitStrokeWidth
?
对于某些图形,比如 Konva.Line
,覆盖 hitFunc
太困难。在某些情况下,你可能只是想让它的事件响应范围更宽一点。这种情况下,最好使用 hitStrokeWidth
属性设置一个较大的值。
操作说明:在星形上进行 mouseover、mouseout、mousedown 和 mouseup 操作,观察命中区域是一个包裹图形的超大圆。对线条也试试看。同时你可以切换命中画布,观察其显示效果,这对调试很有用。
- 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, }); // 自定义命中函数 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 = '切换命中画布'; 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 + ' 星形'); }); line.on('mouseover mouseout mousedown mouseup', function (evt) { writeMessage(evt.type + ' 线条'); }); 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 + ' 星形'); }; const handleLineEvent = (evt) => { setMessage(evt.type + ' 线条'); }; 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)}>切换命中画布</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">切换命中画布</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 + ' 星形'; }; const handleLineEvent = (e) => { message.value = e.type + ' 线条'; }; 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>