如何使画布文字可选?

时间:2009-09-20 18:15:29

标签: javascript text html5 canvas

任何建议都受到高度赞赏。

9 个答案:

答案 0 :(得分:22)

文本选择有许多组件,有些是视觉的,有些是非视觉的。

首先,使文本可选择必须保留数组,文本所在的位置,文本的内容以及使用的字体。您将在Canvas函数measureText中使用此信息。

通过使用measureText和文本字符串,您可以识别单击图像时光标应该落在哪个字母上。

ctx.fillText("My String", 100, 100);
textWidth = ctx.measureText("My String").width;

您仍然需要从“font”属性解析字体高度, 因为它目前未包含在文本指标中。画布文本已对齐 默认为基线。

使用此信息,您现在有一个边界框,您可以检查。 如果光标在边界框内,您现在有一个不幸的推断任务 有意选择哪一封信;应该放置光标的开头。这可能涉及多次调用measureText。

此时你知道光标应该去哪里;你需要存储你的 当然,文本字符串作为文本字符串,在变量中。

一旦定义了范围的起点和终点,就必须绘制 选择指标。这可以在新图层(第二个画布元素)中完成, 或者使用XOR合成模式绘制矩形。它也可以通过 只需清除并重新填充填充矩形顶部的文本即可。

总而言之,Canvas中的文本选择,文本编辑都很难编程,重新使用已经编写的组件是明智的,Bespin是一个很好的例子。

如果我遇到其他公开示例,我会编辑我的帖子。我相信Bespin使用基于网格的选择方法,可能需要等宽字体。字体渲染的连字,字距调整,双向性和其他高级功能需要额外的编程;这是一个复杂的问题。

答案 1 :(得分:5)

由于canvas标签的性质,无法选择在canvas元素中绘制的文本。但是有一些解决方法,例如typefaceJS中使用的解决方法。

另一种解决方案是添加带有定位div元素的文本,而不是使用strokeText或fillText。

答案 2 :(得分:3)

如果你需要有可选择的文本,那么创建一个div或者其他东西要容易得多,并将它放在你希望文本显示的画布上。

canvas没有任何用于选择文本的内置机制,因此您必须推出自己的文本呈现并选择代码 - 这可能相当棘手。

答案 3 :(得分:3)

您可以从 Bespin 获得一些想法。

他们在javascript中使用带有文本选择的画布,滚动条,光标闪烁等实现了文本编辑器。

Source Code

答案 4 :(得分:3)

我建议使用EaselJS库,您可以将每个字母添加为子项,甚至可以将鼠标事件添加到该对象,这是一个了不起的库,请查看它!

答案 5 :(得分:3)

TextInput控件很复杂

首先让我说我不是文本控件方面的专家,但是现在我确定这无关紧要,因为我可以帮助您安全地出入树林。这些事物本质上很复杂,需要对事物如何运作有足够的直觉和知识。但是,您可以检查在senpai-js/senpai-stage存储库here中运行的代码。

我们应该预先定义一些内容:

  • 文本可以是任何有效的unicode字符。您可以使用 this 正则表达式来解析它:/^.$/u
  • 您需要跟踪三种不同类型的文本编辑模式:InsertSelectionBasic(我在库中使用了SelectionState枚举,并检查了insertMode舞台上的媒体资源)
  • 您应该在每一个步骤进行健全性检查,否则您将有未定义的意外行为
  • 大多数人希望文本输入的宽度可以调整,因此,如果计划使用纹理,请确保对文本框的内部使用模式
  • 鼠标/触点碰撞检测很复杂,除非您保证文本输入控件不会旋转
  • 当文本大于水平方向上的文本框时,文本应滚动。我们将其称为textScroll,它始终是负数

现在,我将遍历每个函数以描述其行为,以准确描述文本框控件应如何工作。

碰撞(broadPhase和窄相)

碰撞检测是一个怪物。标准化鼠标和触摸事件之间的点移动是本文中未涉及的复杂野兽。一旦处理了点事件,就必须对矩形执行某种常规的碰撞检测。这意味着要进行AABB碰撞。如果文本框精灵本身已旋转,则必须“取消旋转”该点本身。但是,如果鼠标/触摸点已经位于文本框上方,则我们绕过此检查。这是因为一旦开始选择文本,就希望此函数始终返回true。然后,我们移至arrowingPhase冲突,该冲突实际上将检查“未变换”的鼠标/触摸点是否在文本框的填充范围内。如果是,或者文本框处于活动状态,我们将在此处返回一个真实值。

一旦我们知道鼠标/触摸点在文本框的范围内,就可以将画布的CSS视觉上更改为cursor: text;

pointCollision

当我们在文本框上按下鼠标按钮时,我们需要计算插入符号的移动位置。插入符号的范围为0text.length(含)。请注意,这并不完全正确,因为Unicode字符的长度可以为2。您必须跟踪数组中添加到文本的每个字符,以断言您没有在测量错误的Unicode字符。计算目标索引意味着循环遍历当前文本的每个字符,并将其附加到临时字符串上,每次进行测量,直到所测量的宽度大于当前textScroll +所测量的textWidth。

一旦我们断言该点已落在文本框顶部并设置了起点,就可以启动“选择”模式。拖动点应将选择从起始插入索引移动到新计算出的结束索引。这是双向的。

here显示了一个示例。

keyPresses

Web按键的解决方案是检查KeyEvent的key属性。尽管每个人都说了很多,但是可以通过对上述unicode regex进行测试来测试该文本属性。如果匹配,则可能是实际上已在键盘上按下过该键。这不考虑用于复制和粘贴的ctrl + cctrl + v之类的组合键。这些功能微不足道,由读者自己决定如何实现。

少数几个例外是箭头键:“ ArrowLeft”,“ ArrowRight”等。这些键实际上会修改控件的状态并更改其功能。重要的是要记住,键事件只能由当前的focused控件处理。这意味着您应该检查并确保控件在文本输入过程中聚焦。当然,这发生在比我在库中编写的代码更高的级别上,所以这很简单。

下一个需要解决的问题是每个字符输入应如何修改控件的状态。 keyDown方法识别selectionState并根据其状态调用其他函数。这不是经过优化的伪代码,而是用于清楚起见,非常适合我们描述行为的目的。

选择时按下键

  • 普通按键会替换所选文本的内容
  • selectionStart拼接出来,然后将新键插入文本数组
  • 如果按下“删除”或“退格”键,则拼接出所选内容,并将选择模式返回到NormalCaret
  • 如果按下“向左”或“向右”键,则分别将光标移动到开始或结尾处,除非按下Shift键,否则将选择模式返回到“正常”
  • 如果按下Shift键,那么我们实际上想进一步扩展选择范围
    • 选择起点始终在caretIndex处,实际上,我们使用此组合键向左或向右移动选择终点
    • 如果选择端返回到插入符号索引,我们将selectionState再次返回到Normal
  • “ home”和“ end”键的工作方式相同,仅将插入号分别移至0text.length索引
    • 还请注意,按住Shift键会再次从caretIndex扩展选择范围

在普通模式(插入符号模式)下按下键

  • 在插入符号模式下,我们不会替换任何文本,只是在当前位置插入新字符
  • 使用拼接方法插入与Unicode正则表达式匹配的
  • keydowns
  • 在插入文本后将插入符移到右侧(检查并确保没有超过文本长度)
  • 退格键删除caretIndex - 1处索引之前的一个字符
  • 删除将删除caretIndex处索引后的一个字符
  • 在按住Shift键的同时,文本选择适用于左右键
  • 当不按shift键时,左右分别将插入符号向左和向右移动
  • home键将caretIndex设置为0
  • 结束键将caretIndex设置为text.length

在插入模式下按下键

  • 在插入模式下,我们替换当前在caretIndex处选择的字符
  • 使用拼接方法插入与Unicode正则表达式匹配的
  • keydowns
  • 在插入文本后将插入符移到右侧(检查并确保没有超过文本长度)
  • 在当前选择之前,退格键删除字符
  • 删除将删除当前选定的字符
  • 箭头键正常工作并在正常模式下描述
  • 主页键和结束键正常工作并在正常模式下描述

每帧更新文本框

  • 如果文本框具有焦点,则应开始闪烁插入符号,以使用户知道他们正在编辑文本框中的文本
  • Caret模式下向左或向右移动插入符号时,您应该重新启动闪光机制,以便每次插入符号移动时都能准确显示它们的位置
  • 每30帧或半秒刷新一次插入符号
  • 除非模式为ctx.measureText,否则使用Selection到插入符号索引来测量插入符号沿文本的距离,方法是将文本切成插入符号的位置
  • 在选择模式Selection中测量文本沿多远仍然有用,因为我们一直希望文本选择的结尾对用户可见
  • 考虑到当前的textScroll,确保插入符始终在文本框的可见范围内可见

呈现文本框

  • 先保存上下文ctx.save()(基本画布)
  • 如果您不使用路径绘制文本框,请分别在第一层上绘制文本框的左帽,绘制中间图案和右帽
  • 使用由填充和文本框大小定义的路径来裁剪正方形,以防止文本渗出
  • 转换为x textScroll值,该值应为负数
  • 转换为y midline值,该值应垂直位于文本框的中间
  • 设置字体属性
  • 将文本基线设置为middle,并通过在文本数组上调用text.join("")填充文本
  • 如果存在选择或插入模式,请确保在所选文本后面绘制一个“蓝色”正方形,并反转所选文本的字体颜色(这是很简单的,作为练习留给读者)

最后的笔记

是的,任务很艰巨,但是有时候您只是想给世界一个视觉新颖的引擎,或者是一个具有各种功能的精灵舞台。如果您想让senpai注意到您,请访问senpai-stage回购并提交请求请求。祝您一切顺利!

〜恶魔战队

答案 6 :(得分:2)

FabricJS现在能够与canvas元素之外的对象进行交互 - 例如this demo显示一个链接到canvas元素中加载的图像的按钮。

通过挂钩任何移动事件,获取元素的边界框并重新定位HTML元素,可以在Raphael等其他库中使用相同的方法。

答案 7 :(得分:1)

画布只是一个绘图表面。你渲染,结果是像素。因此,您需要在鼠标事件期间处理的某种数据结构中跟踪已渲染到画布的所有文本的位置。

答案 8 :(得分:1)

一个简单的答案是:使用HTML或SVG而不是画布。除非你真的需要低级控制画布的程度。