首页 > 解决方案 > 在给定开始和结束索引处使用 javascript 在后台创建文本突出显示

问题描述

在使用列表索引突出显示一个 div 中的文本时,我发现了一些困难

例如,我有类似于下面的文本 div:

<div> <!--Highlight at given index in this div -->
   <p>As a friend of Romeo’s, Mercutio supports the Montague’s in the ancient feud. An example of Mercutio defending the Montague’s is when Tybalt, a member of the loathed Capulet family, abuses Romeo and Mercutio intervenes on Romeo’s behalf. Attempting to restore peace, Romeo gets between the two combatants and Mercutio “hath got his mortal hurt” (Page 149; Act 3, Scene 1) on Romeo’s account. In spite of his “life shall pay the forfeit of peace” (page 17; Act 1, Scene 1), Romeo seeks revenge on Tybalt as he loves his murdered friend. As Romeo kills Tybalt out of love for Mercutio, Shakespeare suggests that love conquered the thought of being penalized with death.</p>
   <p>Want to  overlap this highlight too</p>
   <p>Want to  overlap this highlight too</p>
</div>

HTML 转义版本如下所示:

As a friend of Romeo’s, Mercutio supports the Montague’s in the ancient feud. An example of Mercutio defending the Montague’s is when Tybalt, a member of the loathed Capulet family, abuses Romeo and Mercutio intervenes on Romeo’s behalf. Attempting to restore peace, Romeo gets between the two combatants and Mercutio “hath got his mortal hurt” (Page 149; Act 3, Scene 1) on Romeo’s account. In spite of his “life shall pay the forfeit of peace” (page 17; Act 1, Scene 1), Romeo seeks revenge on Tybalt as he loves his murdered friend. As Romeo kills Tybalt out of love for Mercutio, Shakespeare suggests that love conquered the thought of being penalized with death.
Want to  overlap this highlight too
Want to  overlap this highlight too

我有一个亮点列表,如下所示:

[
  {
    "id": "cb8c8875-fba4-4abd-9cca-9cc304b9cba6",
    "start_index": 179,
    "end_index": 184,
    "color": "green",
  },
  {
    "id": "9698dd27-ed20-4824-82b0-1548016b5839",
    "start_index": 6,
    "end_index": 53,
    "color": "yellow",
]

我尝试了什么:

我想知道 Javascript Range对象用于根据给定索引创建范围。这将返回一个可用于获取边界矩形的范围。但是没有知道如何从索引中制作那个Range对象。

我想要一个 javascript 代码在给定的文本索引(start_indexend_index)处添加绝对定位的突出显示 div,如下图所示。

请在下面找到示例输出。

高亮输出

任何帮助,将不胜感激。

如果需要更多信息,请发表评论。

注意和更新

标签: javascripthtmlreactjs

解决方案


免责声明:

我们在跨平台文档查看器 React 应用程序中实现了文本突出显示,用户可以通过触摸添加突出显示,我们在他们下次查看文档时加载它们。不用说,它超级复杂。

最困难的问题是从渲染文本中获取文本位置。我假设您也不必处理该部分,否则答案可能会变得很长。三位主要开发人员投入了几天的时间来获得一个可行的解决方案,因此请注意。

重要的是要认识到源代码中的文本位置(例如,您的数据库中的 HTML 文本)、呈现的(浏览器的node.innerHTML)和可见文本是不同的

在某些情况下,浏览器甚至会插入虚拟节点。如果您需要从一个转换到另一个,您首先必须标准化位置,例如通过遍历内容node.innerHTML并忽略标签、多个空格和换行符。

解决方案:

在文本上方放置框很难实现(跨浏览器),并且不能很好地处理多行文本或调整内容区域的大小。

另一方面,当突出显示在一个标签上下文中开始并在另一个标签上下文中结束时,将标签插入文本不会直接起作用,例如,当您有子标签时<p>Hello <b>World Wide Web</b></p>,您想要突出显示“Hello World”。

诀窍是首先在开始和结束位置插入空体标记标签,然后在第二步中添加高光。当你这样做时,向后浏览内容很重要,因为插入会改变位置。

步骤1

sourceContent在下面的代码中可以是parentNode.innerHTML,但如果你必须使用它,位置可能会由于 HTML 标签、空格和换行符而偏移。因此,如果您有源内容(例如来自数据库),请使用它!

insertHighlightMarkerTags(sourceContent = '', highlights = []) {
    const arrSortByIdx = [];

    // collect marker locations for start and end of highlights
    highlights.forEach((record) => {
        arrSortByIdx.push({
            start: true,
            idx: record.startIndex,
            record,
        });

        arrSortByIdx.push({
            end: true,
            idx: record.endIndex,
            record,
        });
    });

    let processedContent = sourceContent;

    // sort marker location in reverse order, so we can insert them without messing with the index positions
    arrSortByIdx.sort((a, b) => b.idx - a.idx);

    // insert marker locations in reverse order
    arrSortByIdx.forEach((item) => {
        const tagName = item.start ? 'mon' : 'moff';
        const tag = `<${tagName} data-id="${item.record.id}"></${tagName}>`;
        processedContent = processedContent.slice(0, item.idx) + tag + processedContent.slice(item.idx);
    });

    return processedContent;
},

第2步

现在,我们将位置作为内容中的标签。它们仅用作标记,不会显示。但是它们允许我们通过查询它们来创建范围:

getRangeFor(domNode, record) {
    const range = document.createRange();

    const fromElem = domNode.querySelector(`mon[data-id="${record.id}"]`);
    const toElem = domNode.querySelector(`moff[data-id="${record.id}"]`);
    if (fromElem === null || toElem === null) {
        // preselect the whole page and then reduce the selection, if necessary
        range.selectNodeContents(domNode);
    }

    if (fromElem) {
        range.setStartAfter(fromElem);
    }

    if (toElem) {
        range.setEndBefore(toElem);
    }
    return range;
}

第 3 步

现在可以使用出色的wrap-range-text库将文本包装到标记标签中。(有许多类似的库,但由于 Edge 和 IE 中的奇怪错误,大多数都不能跨浏览器工作):

import wrapRange from 'wrap-range-text';

createMarker(range, highlight, start = false) {
    const wrapper = document.createElement('mark');
    wrapper.setAttribute('style', `background-color: ${highlight.color}`);
    wrapper.setAttribute('data-id', record.id);
    wrapper.setAttribute('class', className);
    wrapRange(wrapper, range);
},

推荐阅读