插入符在contenteditable div中输入span元素时引发事件

时间:2018-06-29 08:47:54

标签: javascript html contenteditable dom-events

我有一个内容可编辑的div,其跨度如下:

<div contenteditable="true">some <span>spanned</span> text</div>

我想知道是否可以将事件侦听器附加到span元素本身,该事件侦听器可用于检测插入符号是否在span元素内移动。

我不是在div附有监听程序的地方寻找答案,div每次有活动时都要进行检查,例如此答案的解决方案:

Firing an event when the caret gets within a particular div/span/a tag and also, when the caret leaves the tag

3 个答案:

答案 0 :(得分:2)

了解用户输入位置的最简单方法是 document.getSelection(),它提供了一些节点引用,如 baseNode 和当前 focusNode 以及有用的偏移信息。

然后,您可以在 selectionchange 上使用 document 事件来了解光标何时四处移动,而无需检查鼠标是否正在移动/点击或使用键盘事件。

var out, itm; // saved to spare processor time

document.addEventListener('selectionchange',
function(ev) { // called whenever keycursor moves
  
  // find and save our main HTML objects
  if (!out) out = document.getElementById('out');
  if (!itm) itm = document.querySelector('span');

  // get the user selection informations
  let grab = document.getSelection(),
    text = grab.baseNode ||0, //  <-- clicked here
    node = text.parentNode; // <-- container


  out.innerHTML = // now print where we're at~

  // nowhere near the span, showing a hint
  !node?"Well, try moving around the span ..":

  // peaking inside the output, wrong way
  node == out?"You're grabbing me out ...":

  // checked around, let's check for the node
  (node == itm?'inside ': // inside span itself

  // check if we're selecting the whole node
  grab.containsNode(itm)?'found in ':

  // check if at least we selected a part of it
  grab.containsNode(itm.firstChild)?'partial ':

  'other ') + // seemingly somewhere else ...


  node.nodeName + // write which node we found
  ' > ' + // and show a bit of the contents
  (text.data || text.innerText).substr(0, 24);
})
body > div {border: 1px solid; font-size: xlarge;}
#out {background: black; color: white; margin: 1em}
div > span {color: red;} * {padding: 1ex 1em}
<div contenteditable>
  This is a text before
  <span>this span</span>
  in middle of the text
</div>

<div id="out">focus NODE > contents ...</div>


现在这可能会因为要检查 100 个节点而变得非常混乱...您不想要绑定/定时侦听器或自定义节点,因此我个人建议使用经典事件侦听器。

您可以在选择开始节点上生成 caret 事件,使用:

<块引用>

document.addEventListener('selectionchange', ev => document.getSelection().baseNode.parentNode.dispatchEvent(new Event('caret')))

然后使用经典的 HTML 侦听器:

<块引用>

span.addEventListener('caret', function callback() { /* ... */ });

请注意,您必须检查 neightbor-HTML-children 中的插入符号移动,以了解使用此方法是否超出您的范围;存储哪个节点处于活动状态可能会很有趣,因此您可以按照 Daniel 的回答中提出的 caretIncaretOut ......这是一个简单的实现:

document.addEventListener('selectionchange', ev => {
    var node = (document.getSelection().baseNode||0).parentNode,
        old = document.lastGrab;        if (node === old) return;
    if (old) old.dispatchEvent(new Event('caretOut'));
    (document.lastGrab = node).dispatchEvent(new Event('caretIn'));
});

var node = document.querySelector('span'); // find it the way you want
node.addEventListener('caretIn', function callback() { /* ... */ });
node.addEventListener('caretOut', function callback() { /* ... */ });

希望你现在有你需要的东西;快乐编码:)

答案 1 :(得分:1)

您可以使用MutationObserver。然后在观察者回调中检查characterData的变化。

这里是一个示例:

var MutationObserver = 
		window.MutationObserver || 
		window.WebKitMutationObserver || 
		window.MozMutationObserver;

var config = { childList: true, characterData: true, characterDataOldValue:true, subtree: true };

var target = document
.querySelector('div[contenteditable]')
.getElementsByTagName('span')
.item(0);

var observer = new MutationObserver(function(mutations) {
	mutations.forEach(function(mutation) {
		if (mutation.type === 'characterData') {
			console.clear();
			console.log('old value:', mutation.oldValue);
			console.log('new value:', mutation.target.textContent);
		}
	});
});

observer.observe(target, config);
<div contenteditable="true">some <span>spanned</span> text</div>

要检查插入符是否刚刚进入span,应使用简单的click侦听器来完成这项工作。

var target = document
.querySelector('div[contenteditable]')
.getElementsByTagName('span')
.item(0)
.addEventListener('click', function(evt) {
	console.log('caret entered:', evt.target)
})
<div contenteditable="true">some <span>spanned</span> text</div>

答案 2 :(得分:0)

实际上,在考虑时-我真的找不到任何合适的本地事件来解决此问题。

问题是...插入符可以通过多种方式设置,单击,选择(可以在您要检测的区域开始或结束),移动光标的键盘事件甚至是外部脚本。

没有可以捕获所有事件的事件。

可悲的是,我相信这样做的唯一方法是循环并检查当前选择(保存插入符号数据)是否匹配良好。

为实现您的期望,我使用CustomElements实现了一个新元素, 实现主要是让您使用所需的任何语法- 是否为:myElement.dispatchEvent('caretin') 或仅使用html标签语法<caret-detector oncaretin="myFunction(event,'myArg')"></caret-detector>

我并不是说这是最好的主意,也不是当前想法的最佳实现,但我认为它涵盖了使用一个循环和本机js / html方法的所有情况。

//Create Appropriate Events And initialize them
var CaretInEvent = document.createEvent('Event'),
		CaretOutEvent = document.createEvent('Event');
CaretInEvent.initEvent('caretin',true,true);
CaretOutEvent.initEvent('caretout',true,true);

//Next section will provide the caret handling functionallity
const CaretDetectorTagName = "caret-detector";
class CaretDetector extends HTMLElement {
      //these will be used in order to support switching element in focus
	static get lastDetected(){
		return CaretDetector._lastDetected;
	}
	static set lastDetected(element){
		CaretDetector._lastDetected = element;
	}
    //the interval
	static detectorStart(){
		setInterval(function(){
			let focusNode = document.getSelection().focusNode;
			if(focusNode){
				focusNode = focusNode.parentNode.closest(CaretDetectorTagName) || focusNode.parentNode;
				if(CaretDetector.lastDetected && focusNode !== CaretDetector.lastDetected && CaretDetector.lastDetected.inStatus === true && CaretDetector.lastDetected.triggerCaretOut){
					CaretDetector.lastDetected.dispatchEvent(CaretOutEvent);
				}
				if(focusNode.triggerCaretIn && focusNode.inStatus !== true){
					focusNode.dispatchEvent(CaretInEvent);
					CaretDetector.lastDetected = focusNode;
				}
			}
		},100);
	}
	//start with listening to the mentioned events
	constructor(){
		super();
		this._instatus = false;
		this.addEventListener('caretin', (...args)=>{
			this._instatus = true;
			this.triggerCaretIn(...args);
		});
		this.addEventListener('caretout', (...args)=>{
			this._instatus = false;
			this.triggerCaretOut(...args);
		});
	}
			
	get inStatus(){
		return this._instatus;
	}
			
	triggerCaretIn(event){
		let desiredFn = this.getAttribute('oncaretin'),
		wrapperFunction = new Function('event',`${desiredFn};`);
		wrapperFunction(event);	
	}
			
	triggerCaretOut(event){
		let desiredFn = this.getAttribute('oncaretout'),
		wrapperFunction = new Function('event',`${desiredFn};`);
		wrapperFunction(event);	
	}
}
    
//actually connect the tag-name with the above class
customElements.define(CaretDetectorTagName, CaretDetector);

//the functions you want to execute when caret is in or out
function inFn(event){
	console.log(event.srcElement.id + ": in");
}

function outFn(event){
	console.log(event.srcElement.id + ": out");
}
		
window.onload = CaretDetector.detectorStart;
<div contenteditable="true">
	012345
	<caret-detector id="object1" oncaretin="inFn(event)" oncaretout="outFn(event)">STRING OF OBJECT 1</caret-detector>
	<caret-detector id="object2" oncaretin="inFn(event)" oncaretout="outFn(event)">STRING OF OBJECT 2</caret-detector>
</div>