如何以编程方式仅选择用户可选择的所有文本节点

Posted

技术标签:

【中文标题】如何以编程方式仅选择用户可选择的所有文本节点【英文标题】:How to programmatically only select all text nodes that are selectable by the user 【发布时间】:2019-06-07 05:43:42 【问题描述】:

我需要使用 DOM 选择 API getSelection 选择 html 文档中的所有节点。 用户不能选择的节点(即用鼠标)应该被排除在外。 因此,如果一个元素应用了 CSS 规则 user-select: none-moz-user-select: none,我的编程选择应该排除这些元素。

如果我手动(通过鼠标)选择文本,则不会选择这些元素。如果我在其父元素之一上应用window.getSelection().selectAllChildren,那么不可选择的元素也会被选中。

我尝试了 SelectionRange 对象的不同方法,但还没有找到一种方法来仅以编程方式选择那些可以手动选择的元素。

<body>
    <div>Selectable</div>
    <div style="-moz-user-select:none">
        <span id="span">Non-Selectable</span>
    </div>

    <script>
        const sel = window.getSelection();
        sel.selectAllChildren(document.body);
        console.log(sel.containsNode(document.getElementById('span')));
        // outputs true
    </script>
</body>  

有谁知道以编程方式仅选择那些可以手动选择的元素的方法?

编辑所以我需要的是一个函数,它接收一个节点作为参数,并在该节点是否可选时返回一个布尔值:

function isSelectable(node) 
    // determine if node is selectable

【问题讨论】:

您是否尝试遍历所有元素并仅根据所需条件选择那些元素? @DejanDozet:我将不得不与window.getComputedStyle(node).getPropertyValue('-moz-user-select') 核对 - 但也适用于节点的所有父节点。更不用说还有user-select-ms-user-select,...。而且这对于我的目的来说太耗时了 @DejanDozet:编辑:我的代码正在遍历所有已经使用NodeIterator 的文本节点。如果有一种简单的方法可以检查节点是否可以手动选择,那么这将起到作用。但我不知道如何 如果可以快速确定给定节点是否是用户可选择的,它将解决我的问题。也许最好为此打开一个新问题 你能不能把 noselect 类添加到每个 no-select 元素然后得到你想要的结果,你可以看看这个帖子:***.com/a/9314458/4541566 ,也许还有那个页面上的其他帖子 【参考方案1】:

这是一种可能的方式,无需遍历节点的祖先:

function isSelectable(textNode) 
    const selection = getSelection();
    selection.selectAllChildren(textNode.parentNode);
    const selectable = !!selection.toString();
    selection.collapseToStart();
    return selectable;

说明: 如果节点不是用户可选择的,您仍然可以通过编程方式选择它 (selectAllChildren),但 toString() 无论如何都不会包含节点的文本内容。 在我的情况下,我需要遍历 document.body 的所有文本节点,不幸的是,这个解决方案对于我的目的来说仍然太慢

【讨论】:

【参考方案2】:

可能是这样的:

var userselect = [
    '-webkit-touch-callout', /* ios Safari */
    '-webkit-user-select', /* Safari */
    '-khtml-user-select', /* Konqueror HTML */
    '-moz-user-select', /* Firefox */
    '-ms-user-select', /* Internet Explorer/Edge */
    'user-select'
];
function isSelectable(element) 
    var style = getComputedStyle(element);
    var canSelect = !userselect.some(key => style[key] === 'none');
    if(canSelect) 
        if(element.parentElement) return isSelectable(element.parentElement);
        return true;
    
    return false;

基本上,如果此元素或其任何祖先是不可选择的,则此元素是不可选择的。我们检查这个元素,然后使用递归来检查祖先元素,当我们用完祖先或找到一个设置为不可选择的祖先时停止。

我对用户选择如何工作的假设可能是错误的;即使在设置祖先不可选择之后,也可能强制内部元素可选择。可以重新组织逻辑以减少混乱。当然可以使用循环来删除递归。 userselect 数组可以使用一些智能;如果这是用于扩展,您可以使用它来通知您需要检查哪些属性。这段代码需要一个元素而不是一个节点。我还没有实际测试过这段代码,但它似乎应该可以工作。

【讨论】:

感谢您的回答:) 是的,它用于扩展。我知道我可以通过遍历其所有祖先检查getComputedStyle(node).MozUserSelect 来确定一个元素是否可选,但不幸的是,这是一种耗时的方式,因为我会为body 中的每个节点执行此操作。我希望有一个简单的检查,而不必遍历所有祖先;) @user1521685 没有。如果您提供了一个用例,或许可以提出替代方案。【参考方案3】:

好吧,因为我怀疑你的代码部分好(99% 好),那是因为不同的浏览器,结合你的脚本和我已经发送给你的链接我管理这个:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <style>
    .noselect 
  -webkit-touch-callout: none; /* iOS Safari */
    -webkit-user-select: none; /* Safari */
     -khtml-user-select: none; /* Konqueror HTML */
       -moz-user-select: none; /* Firefox */
        -ms-user-select: none; /* Internet Explorer/Edge */
            user-select: none; /* Non-prefixed version, currently
                                  supported by Chrome and Opera */

  </style>
</head>
<body>
    <div>Selectable</div>
    <div class="noselect">
        <span id="span">Non-Selectable</span>
    </div>
    <div id="r">
    </div>


    <script>
      window.onload = function() 
        var sel = window.getSelection();
        sel.selectAllChildren(document.body);
        document.getElementById('r').innerHTML = sel.containsNode(document.getElementById('span'));
        // outputs true
      ;

    </script>
</body>
</html>

当你在这里运行它时,你会发现它有效!我的意思是 -moz-user-select: none;只适用于火狐...

在说我也检查过其他浏览器(IE、Firefox、Chrome 和 Edge)之后,这里只适用于 Chrome。

【讨论】:

非常感谢您的帮助! :) 不幸的是,我仍然看不出这如何帮助确定节点是否可选。在您的示例中,我可以判断它是否可以选择,因为 noselect 类 - 但大多数网页对我来说并不那么容易 所以,我需要的是这样的函数:isSelectable(node) ... return selectable 是的,因为它是特定于浏览器的,我认为我们应该开始为每个浏览器分别构建该功能。就像在 chrome noselect 上一样,你的 javascript 工作得很好,但在 firefox、edge 和 IE 上却不行。 (我使用 noselect 是因为它适用于所有浏览器供用户选择)

以上是关于如何以编程方式仅选择用户可选择的所有文本节点的主要内容,如果未能解决你的问题,请参考以下文章

如何以编程方式使文本可选择并提供弹出菜单以转到另一个应用程序

Java:如何以编程方式选择和扩展 JTree 中的多个节点?

在 iOS 14 中以编程方式打开 UIDatePicker

Sencha Touch:如何以编程方式关注 ios 上的文本字段?

以编程方式读取在控制台窗口中选择的当前文本

如何使用 jQuery 选择文本节点?