我可以设置一个事件侦听器来告诉我何时在 HTML 文档中的某个位置发生了鼠标单击。但是如果点击发生在某些文本上,我需要知道点击发生在文本中的哪个字符。有没有办法做到这一点?
我能想到一些非常令人讨厌的解决方案。例如,对于文档中的每一个字符,我都可以将它包装在一个带有自己事件的单独元素中。或者,因为我可以知道点击发生在哪个文本节点,所以我可以使用 clientWidth 执行某种计算(基本上几乎就像模拟文本的渲染),以确定点击发生在哪个字符中。
肯定有更容易的事情吗?
我可以设置一个事件侦听器来告诉我何时在 HTML 文档中的某个位置发生了鼠标单击。但是如果点击发生在某些文本上,我需要知道点击发生在文本中的哪个字符。有没有办法做到这一点?
我能想到一些非常令人讨厌的解决方案。例如,对于文档中的每一个字符,我都可以将它包装在一个带有自己事件的单独元素中。或者,因为我可以知道点击发生在哪个文本节点,所以我可以使用 clientWidth 执行某种计算(基本上几乎就像模拟文本的渲染),以确定点击发生在哪个字符中。
肯定有更容易的事情吗?
捕获鼠标事件后,将元素中的文本拆分为两个单独的跨度。查看每个跨度的偏移量以确定事件发生的位置。现在将该跨度一分为二并再次比较。重复,直到您有一个具有单个字符的跨度,其坐标包含鼠标单击的坐标。由于这本质上是一个二分搜索,因此整个过程将相当快,并且与替代方案相比,跨度的总数较低。找到字符后,可以解散跨度并将所有文本返回到原始元素。
不幸的是,您必须将每个字符包装在一个元素中,但您不必为每个字符附加一个事件侦听器。当在click
元素上触发事件时,它会冒泡到其父级。target
然后,您可以使用事件的属性检索实际单击的元素。
假设我们在名为 的元素中有一些文本textElement
。它包含span
每个字符的一个。如果我们希望能够单击字符来删除它们,我们可以使用以下代码:
textElement.addEventListener('click', function(e) {
textElement.removeChild(e.target);
}, false);
这是我为实现迈克尔在他的回答中所写的内容所做的努力:
function hitCharBinSearch(mClientX, inmostHitEl) {
const originalInmost = inmostHitEl
const bareText = inmostHitEl.firstChild.textContent
var textNode = inmostHitEl.firstChild
var textLenghtBeforeHit = 0
do {
let textNodeR = textNode.splitText(textNode.length / 2)
let textNodeL = textNode
let spanL = document.createElement('span')
spanL.appendChild(textNodeL)
let spanR = document.createElement('span')
spanR.appendChild(textNodeR)
inmostHitEl.appendChild(spanL)
inmostHitEl.appendChild(spanR)
if (mClientX >= spanR.getBoundingClientRect().left) {
textNode = textNodeR
inmostHitEl = spanR
textLenghtBeforeHit += textNodeL.length
}
else {
textNode = textNodeL
inmostHitEl = spanL
}
} while (textNode.length > 1)
/* This is for proper caret simulation. Can be omitted */
var rect = inmostHitEl.getBoundingClientRect()
if (mClientX >= (rect.left + rect.width / 2))
textLenghtBeforeHit++
/*******************************************************/
originalInmost.innerHTML = bareText
return textLenghtBeforeHit
}
Placing each character in a document model object is not as obnoxious as it sounds. HTML parsing, DOM representation, and event handling is quite efficient in terms of memory and processing in modern browsers. A similar mechanism is used at a low level to render the characters too. To simulate what the browser does at that level takes much work.
This example is lightweight, loads quickly, and is portable across common browsers. Its elegance is not immediately apparent, much reliability is gained by establishing a one to one correspondence between international characters and event listeners.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Character Click Demo</title>
<script type='text/javascript'>
var pre = "<div onclick='charClick(this, ";
var inf = ")'>";
var suf = "</div>";
function charClick(el, i) {
var p = el.parentNode.id;
var s = "para '" + p + "' idx " + i + " click";
ele = document.getElementById('result');
ele.innerHTML = s; }
function initCharClick(ids) {
var el; var from; var length; var to; var cc;
var idArray = ids.split(" ");
var idQty = idArray.length;
for (var j = 0; j < idQty; ++ j) {
el = document.getElementById(idArray[j]);
from = unescape(el.innerHTML);
length = from.length;
to = "";
for (var i = 0; i < length; ++ i) {
cc = from.charAt(i);
to = to + pre + i + inf + cc + suf; }
el.innerHTML = to; } }
</script>
<style>
.characters div {
padding: 0;
margin: 0;
display: inline }
</style>
</head>
<body class='characters' onload='initCharClick("h1 p0 p2")'>
<h1 id='h1'>Character Click Demo</h1>
<p id='p0'>æ€ – ࿗Ø —</p>
<p id='p1'>Next E para.</p>
<p id='p2'>© 2017</p>
<hr>
<p id='result'> </p>
</body>
</html>
[1] This simple example does not have handling for surrogate pairs, but such could be added in the body of the i loop.