焦点输入时保持文本处于选中状态

Keep text selected when focus input

本文关键字:于选中 状态 文本 输入 焦点      更新时间:2023-09-26

这个问题已经被问过了,但直到现在还没有有效的答案,所以我很想再次打开它,希望我们能找到一个技巧。

我有一个内容可编辑的段落和一个文本输入,当我选择一些文本并单击输入时,选择就消失了。

所以我尝试在输入鼠标按下时保存选择并在鼠标向上恢复它,是的,它可以工作(正如火狐中预期的那样(但是......在 Chrome 中,输入失去焦点:(

查看它的实际效果(使用铬(:https://jsfiddle.net/mody5/noygdhdu/

这是我使用的代码:

.HTML

<p contenteditable="true">
    Select something up here and click the input below
    <br> on firefox the input get the focus and the text still selected.
    <br> on chrome the text still selected but the input lose focus
</p>
    <input type="text" id="special" style="border: solid blue 1px">

JavaScript

function saveSelection() {
    if (window.getSelection) {
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            return sel.getRangeAt(0);
        }
    } else if (document.selection && document.selection.createRange) {
        return document.selection.createRange();
    }
    return null;
}
function restoreSelection(range) {
    if (range) {
        if (window.getSelection) {
            sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (document.selection && range.select) {
            range.select();
        }
    }
}

var specialDiv = document.getElementById("special");
var savedSel = null;
specialDiv.onmousedown = function() {
    savedSel = saveSelection(); // save the selection
};
specialDiv.onmouseup = function() {
    restoreSelection(savedSel); // restore the selection
};

<span>替换选择可能是最简单的方法。您还可以使用 <iframe> ,这是谷歌在谷歌文档中用来在点击 UI 元素时保持文档中文本选择的方法。

使用 <span> ,解决方案可能是这样的(此解决方案基于您的原始代码和这里其他人的想法,尤其是 Bacaj @Bekim(。

!function(doc, win) {
  var input = doc.getElementById('special')
  	, editable = doc.getElementById('editable')
    , button = doc.getElementById('button')
    , fragment = null
    , range = null;
	function saveSelection() {  
    if (win.getSelection) {
      sel = win.getSelection();
      if (sel.getRangeAt && sel.rangeCount) {
        return sel.getRangeAt(0);
      }
    } else if (doc.selection && doc.selection.createRange) {
      return doc.selection.createRange();
    }
    return null;
  }
  
  /* Not needed, unless you want also restore selection
  function restoreSelection() {
    if (range) {
      if (win.getSelection) {
        sel = win.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      } else if (doc.selection && range.select) {
        range.select();
      }
    }
  }
  */
    
  function saveRangeEvent(event) {
    range = saveSelection();
    if (range && !range.collapsed) {
    	fragment = range.cloneContents();
      toggleButton();
    }
  } 
   
  function toggleButton() {
      button.disabled = !fragment || !input.value.match(/^https?:.*/);
  }
  toggleButton();
  
  editable.addEventListener('mouseup', saveRangeEvent);
  editable.addEventListener('keyup', saveRangeEvent);
  button.addEventListener('click', function(event) {
    // insert link
  	var link = doc.createElement('a');
    link.href = input.value;
    input.value = '';
    range.surroundContents(link);
    toggleButton();
  });
  input.addEventListener('keyup', toggleButton);
  input.addEventListener('change', toggleButton);
  input.addEventListener('mousedown', function(event) {
    // create fake selection
    if (fragment) {
      var span = doc.createElement('span');
      span.className = 'selected';
      range.surroundContents(span);
    }
  });
  input.addEventListener('blur', function(event) {
    // remove fake selection
  	if (fragment) {
      range.deleteContents();
      range.insertNode(fragment);  
      //restoreSelection();
    }
    fragment = null;
  }, true);
  
}(document, window)
    
    
.selected {
  background-color: dodgerblue;
  color: white;
}
<p id="editable" contenteditable="true">
  Select something up here and click the input below
  <br>on firefox the input get the focus and the text still selected.
  <br>on chrome the text still selected but the input lose focus
</p>
<table>
  <tr>
    <td>
      <input type="text" id="special" style="border: solid blue 1px" placeholder="insert valid link incl. http://">
    </td>
    <td>
      <button id="button">Add link</button>
    </td>
  </tr>
</table>


链接到 jsFiddle

由于我无法评论maioman(:)需要一些声誉(,因此在这里对他的aswer进行一些补充:

它在 Firefox 中不起作用的原因是将焦点放在输入字段上会删除选择。

如果您在 p 上放置鼠标向上事件而不是在输入字段上放置焦点事件,则一切正常:

   p.addEventListener('mouseup', (( => {      突出显示(选择(((;保存所选内容    })

我在这个上做了一些工作......这是一个非常有趣和有启发性的练习。
我主要从Maioman的回答开始。

我这样做是为了让选定的文本最终出现在输入字段中提供的 href 的锚点中......输入链接时,所选文本将保持选中状态。这是我对你问题的理解。

查看我的工作小提琴:https://jsfiddle.net/Bes7weB/rLmfb043/
经测试可在FF 46,Chrome 50,Safari 5.1和Explorer 11上运行。

请注意,classList仅在 IE10 及更高版本中受支持。
此外,由于鼠标向上事件,链接不是"可单击的"。
但是您可以在鼠标悬停时看到标题属性。
我假设您将保存段落的内部 HTML 以将其输出到其他地方。

;)

.CSS:

a.highlighted {
  background: blue;
  color:white;
}

.HTML:

<h1>Select some text below and click GO!</h1>
<br>
<p contenteditable="true" tabindex="0">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris nec risus turpis. Donec nisi urna, semper nec ex ac, mollis egestas risus. Donec congue metus massa, nec lacinia tortor ornare ac. Nulla porttitor feugiat lectus ut iaculis. In sagittis tortor et diam feugiat fermentum. Nunc justo ligula, feugiat dignissim consectetur non, tristique vitae enim. Curabitur et cursus velit. Etiam et aliquam urna. Duis pharetra fermentum lectus et fermentum. Phasellus eget nunc ultricies, ornare libero quis, porta justo. Sed euismod, arcu sed tempor venenatis, urna ipsum lacinia eros, ac iaculis leo risus ac est. In hac habitasse platea dictumst. Sed tincidunt rutrum elit, ornare posuere lorem tempor quis. Proin tincidunt, lorem ac luctus dictum, dui mi molestie neque, a sagittis purus leo a nunc.
</p><br>
<br>
<b>Add a link to selected text:</b> <input type="text" id="hrefInput" style="border: solid blue 1px" value="http://www.test.com"> <input type="button" id="gobutton" value="GO!"><br>
<span id="errorMsg" style="display:none;">No selected text!</span><br>
<input type="button" id="undoButton" value="Undo">

JavaScript:

var p = document.querySelector('p');
var old = p.innerHTML;
var HrefInput = document.getElementById("hrefInput");
var GoButton = document.getElementById("gobutton");
var UndoButton = document.getElementById("undoButton");
var errorMsg = document.getElementById("errorMsg");
var idCounter=0;
var textSelected=false;
UndoButton.addEventListener('focus', function() {
    console.log("Undo button clicked. Default text reloaded.");
    restore();
})
GoButton.addEventListener('click', function() {
    if(!textSelected){
        errorMsg.style.display="inline";
        errorMsg.style.color="rgb(166, 0, 0)";
        errorMsg.style.fontWeight="bold";
        return;
    }
    console.log("GO button clicked: Link id=a-"+idCounter+" created.");
    targetId="a-"+idCounter;
    document.getElementById(targetId).setAttribute("href",HrefInput.value);
    document.getElementById(targetId).classList.add("createdlink");
    document.getElementById(targetId).setAttribute("title",HrefInput.value);
    document.getElementById(targetId).classList.remove("highlighted");
    textSelected=false;
    idCounter++
})
p.addEventListener('focus', function() {
    errorMsg.style.display="none";
});
p.addEventListener('mouseup', function() {
    textSelected=true;
    console.log("Mouseup event in p : Text selected.");
    appendanchor(selectText()); // extract the selection
    HrefInput.focus();  // FireFox 
    HrefInput.blur();   // Needs it. Try without, you'll see.
})
function appendanchor(r) {  // onmouseup
    if (!r) return;
    extracted = r.extractContents();
    el = document.createElement('a');
    el.setAttribute("id", "a-"+idCounter);
    el.setAttribute("class", "highlighted");
    el.appendChild(extracted);
    r.insertNode(el)
}
function selectText() { // onmouseup
    if (window.getSelection) {
        console.log("window.getSelection");
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) { // Chrome, FF
            console.log(sel.getRangeAt(0));
            return sel.getRangeAt(0);
        }
        else{console.log(sel);}
    } else if (document.selection && document.selection.createRange) {
        console.log("elseif");
        return document.selection.createRange();
    }
    return null;
}
function restore() {
    p.innerHTML = old;
    textSelected=false;
}

用 span 元素替换所选区域(并为其着色(可能是一种解决方法:

var p = document.querySelector('p');
var old = p.innerHTML;
var input = document.querySelector('input');
p.addEventListener('blur', () => {
   highlight(select()); // save the selection
})
p.addEventListener('focus', () => {
  restore(); // restore the selection
})
function highlight(r) {
  if (!r) return;
  var extracted = r.extractContents();
  el = document.createElement('span');
  el.appendChild(extracted);
  r.insertNode(el)
}
function select() {
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
      return sel.getRangeAt(0);
    }
  } else if (document.selection && document.selection.createRange) {
    return document.selection.createRange();
  }
  return null;
}
function restore() {
  p.innerHTML = old;
}
span {
  background: tomato;
  color:white;
}
<p contenteditable="true" tabindex="0">
  Select something up here and click the input below
  <br> on firefox the input get the focus and the text still selected.
  <br> on chrome the text still selected but the input lose focus
</p>
<input type="text" id="special" style="border: solid blue 1px">

适用于铬,但不适用于 FF

正如 Eric 建议的那样mouseupp上使用事件(我实际上使用了 blur (来调用highlight(select())这将解决 FF 上的问题。

超时函数中添加焦点,这应该可以解决您的问题。

setTimeout(function(){
  document.getElementById("textToInsert").focus();
    }, 1);

JSFIDDLE: http://jsfiddle.net/mody5/L5hx9h3k/1/

这是我很久以前在其他列表中作为答案发布的一个非常古老的片段。它可能会帮助你重新思考你当前的策略,并完全避免黑客攻击自然预期的专注行为的需要。

function createLink(e){
 if(e.target){ 
	var a = window.getSelection().getRangeAt(0);
	var b = a.toString();  
	var z = document.createElement("span");
	var l2 = prompt("Enter URL:", "http://");
		b = b.link(l2);
		z.innerHTML=b;
		a.deleteContents();
		a.insertNode(z) }
 else{
        document.execCommand("CreateLink") }
}
<!DOCTYPE html>
<html>
<head>
<title>Text to Hyperlink</title> 
</head> 
<body>
<h1>Create a link</h1> 
Select some text and click the button. On the presented toolbox provide the url and confirm. The selected text will become a hyperlink<br>
My Homepage<br>
My Favorite<br>
My Search Page<br><br>
<button onclick="createLink(event)">Make it a link</button>
<script>
</script>
</body>
</html>

我会给你一个提示,让你自己弄清楚。您需要检测是否正在使用镶边。在您的 jsfiddle 中,在 sel = window.getSelection(); 之后添加 console.log(sel); 。请注意,日志中的选择在不同的浏览器上是不同的。老实说,我不确定为什么,但这可以帮助您找出问题所在。

如果您注释掉sel.removeAllRanges();,也请注意同样的问题,您将收到一个错误,告诉您它们是不同的,如上所述。

在我的用例中,我能够在MutationObserver的帮助下解决使用输入字段时丢失选择的问题。

在我的组件中,我有一个连接时初始化的范围的状态:

private range: Range | undefined;
componentWillLoad() {
    const selection: Selection | undefined = getSelection();
    this.range = selection?.getRangeAt(0);
}

getSelection是一个实用程序,它根据浏览器返回Selection

然后应用颜色的函数如下所示:

private selectColor($event: CustomEvent) {
    const selection: Selection | undefined = getSelection();
    if (!selection || !$event || !$event.detail) {
      return;
    }
    selection?.removeAllRanges();
    selection?.addRange(this.range);
    const observer: MutationObserver = 
      new MutationObserver( (_mutations: MutationRecord[]) => {
      observer.disconnect();
      this.range = selection?.getRangeAt(0);
    });
    const anchorNode: HTMLElement | undefined = getAnchorNode(selection);
    observer.observe(anchorNode, {childList: true});
    document.execCommand('foreColor', false, $event.detail.value);

发生了什么:我得到选择,删除所有范围并将我保留的范围添加为状态。

然后,我在选择锚节点上附加一个突变观察器。为此,我使用了一个实用程序,该实用程序在选择text或其comment的情况下返回锚节点或其父节点。

然后我打电话给execCommand.

一旦观察者启动,我查询新范围的选择(此时是文档的修改节点,而不是输入(并将其保存到我的状态。