我正在构建一个Web应用程序,使用fillText在HTML 5 Canvas上绘制一组不同字体的字母.用户将单击该画布上的某个位置,我需要检查他们点击了哪个字母(或者如果他们点击了一个字母).
我想我需要:
>获取每个字母的矢量路径(我不知道如何做到这一点).
>使用一些简单的碰撞检测算法检查点击点是否在字母路径内.
有一些简单的功能可以做到这一点,我错过了吗?或者像这样的事情的图书馆?如果没有任何库,我如何获取特定字体的字母路径来自行检查?
我需要使用字母的实际形状而不仅仅是它的边界框,因为我不希望用户能够在O的中间单击并将其注册为命中.
任何这方面的提示都将受到赞赏.
解决方法:
逻辑
如果不为它提供自定义逻辑,则无法在画布上处理单独的字母.绘制到画布上的所有内容都合并为一堆像素.
不幸的是,您无法将文本添加为纯路径,因此您必须检查像素值.否则,您只需将文本添加到新路径,并对每个字母使用isPointInPath方法.
一种方法
我们无法在此处提供完整的解决方案,但是这里有一个基础,您可以在此基础上提供基本逻辑来单击画布上的单个字母:
>每个字母都存储为对象incl.它的位置,大小,字体和字符,还有一个矩形命中区域(见下文)
>使用这些对象定义数组,然后将它们传递给渲染函数
>当你注册一个点击迭代数组并测试矩形命中区域,如果在里面检查像素(*)
*)要区分重叠字母,您需要优先检查.您还可以将此char渲染到单独的画布上,以便只获得此char的像素.我没有在演示中展示这一点,但你会明白这一点.
演示
var ltrs = []; /// stores the letter objects
/// Create some random objects
for(;i < 20; i++) {
/// build the object
var o = {char: alpha[((alpha.length - 1) * Math.random())|0],
x: ((w - 20) * Math.random())|0,
y: ((h - 20) * Math.random())|0,
size: (50 * Math.random() + 16)|0,
font: fonts[((fonts.length - 1) * Math.random())|0]};
/// store other things such as color etc.
/// store it in array
ltrs.push(o);
}
然后我们有一些函数来渲染这些字符(参见演示).
当我们处理点击时,我们遍历对象数组并首先检查边界以检查我们所处的字母(在这里选择一个像素将不会使我们识别字母):
demo.onclick = function(e) {
/// adjust mouse position to be relative to canvas
var rect = demo.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, o;
/// iterate
for(;o = ltrs[i]; i++) {
/// is in rectangle? "Older" letters has higher priority here...
if (x > o.x && x < (o.x + o.rect[2]) &&
y > o.y && y < (o.y + o.rect[3])) {
/// it is, check if we actually clicked a letter
/// This is what you would adopt to be on a separate canvas...
if (checkPixel(x, y) === true) {
setLetterObject(o, '#f00')
return;
}
}
}
}
像素检查是直接的,它在x / y位置选取一个像素并检查其alpha值(如果使用纯色背景,则检查颜色):
function checkPixel(x, y) {
var data = ctx.getImageData(x, y, 1, 1).data;
return (data[3] !== 0);
}
更新了检查像素功能:
这个更新的检查能够检查字母,即使它们在同一区域重叠.
我们创建了一个单独的画布来绘制字母.这隔离了字母,当我们选择一个像素时,我们只能从该特定字母中获取一个像素.我们的屏幕外画布仅在检查期间设置字母而不是背景的像素,这也与我们的背景颜色无关.开销很小.
function checkPixel(o, x, y) {
/// create off-screen canvas
var oc = document.createElement('canvas'),
octx = oc.getContext('2d'),
data,
oldX = o.x,
oldY = o.y;
/// default canvas is 300x150, adjust if letter size is larger *)
//oc.width = oc.height = 200;
/// this can be refactored to something better but for demo...
o.x = 0;
o.y = 0;
setLetterObject(octx, o, '#000');
o.x = oldX;
o.y = oldY;
data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
return (data[3] !== 0);
}
*)当我们创建画布时,默认大小为300×150.为了避免重新分配新的位图,我们只是保留它,因为已经为它分配了内存,我们只需要从中选择一个像素.如果字母的像素大小大于默认大小,我们当然需要重新分配以使字母合适.
在这个演示中,我们临时覆盖x和y位置.对于生产,您应该启用setLetterObject方法以某种方式覆盖它,因为它会更优雅.但我会在演示中将其保留为原样,因为最重要的是理解原理.