令人满意的单行输入

时间:2011-07-26 14:19:23

标签: javascript jquery html css contenteditable

对于我在我工作的公司开发的应用程序,我们需要一个支持在基于JS的Web应用程序中插入表情符号的输入。我们目前正在使用带有表情符号短代码(即':-)')的输入,并希望切换到插入实际的图形图像。

我们最初的计划是使用contenteditable <div>。我们正在使用粘贴事件的监听器以及不同的键/鼠标交互来确保没有不需要的标记进入contenteditable(我们从其容器标签中删除文本并仅留下我们自己插入的图像标签)。

然而,现在的问题是,如果你输入足够的内容(即它的高度增加),div会调整大小。我们不希望这种情况发生,也不接受隐藏文本(即普通overflow: hidden)。所以:

有没有办法让contenteditable div表现得像单行输入?

我最喜欢,如果有一个相对简单的属性/ css属性,我错过了会做我想要的,但如果有必要,CSS + JS建议也将受到赞赏。

10 个答案:

答案 0 :(得分:96)

[contenteditable="true"].single-line {
    white-space: nowrap;
    width:200px;
    overflow: hidden;
} 
[contenteditable="true"].single-line br {
    display:none;

}
[contenteditable="true"].single-line * {
    display:inline;
    white-space:nowrap;
}
<div contenteditable="true" class="single-line">
    This should work.
</div>​

答案 1 :(得分:6)

我认为您正在寻找一个contenteditable div,只有一行文字在溢出div时会水平滚动。这应该可以解决问题:http://jsfiddle.net/F6C9T/1

div {
    font-family: Arial;
    font-size: 18px;
    min-height: 40px;
    width: 300px;
    border: 1px solid red;
    overflow: hidden;
    white-space: nowrap;
}
<div contenteditable>
    Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
</div>

min-height: 40px包含水平滚动条出现时的高度。当水平滚动条出现时,min-height:20px会自动展开,但这在IE7中不起作用(尽管您可以使用条件注释来应用单独的样式)。

答案 2 :(得分:3)

这是一个相对简单的解决方案,它使用contenteditable的输入事件来扫描dom并过滤掉各种新线条(因此它应该可以防止复制/粘贴,拖动&n; n跌落,击中键盘输入等)。它将多个TextNode压缩为单个TextNodes,从TextNodes中删除换行符,杀死BR,并放置一个&#34; display:inline&#34;在它接触的任何其他元素上。在Chrome上测试过,在其他任何地方都无法保证。

&#13;
&#13;
var editable = $('#editable')

editable.on('input', function() {
  return filter_newlines(editable);
});


function filter_newlines(div) {
    var node, prev, _i, _len, _ref, _results;
    prev = null;
    _ref = div.contents();
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        node = _ref[_i];
        if (node.nodeType === 3) {
            node.nodeValue = node.nodeValue.replace('\n', '');
            if (prev) {
                node.nodeValue = prev.nodeValue + node.nodeValue;
                $(prev).remove();
            }
            _results.push(prev = node);
        } else if (node.tagName.toLowerCase() === 'br') {
            _results.push($(node).remove());
        } else {
            $(node).css('display', 'inline');
            filter_newlines($(node));
            _results.push(prev = null);
        }
    }
    return _results;
}
&#13;
#editable {
    width: 200px;
    height: 20px;
    border: 1px solid black;
}
&#13;
<div id="editable" contenteditable="true"></div>
&#13;
&#13;
&#13;

或者这里是小提琴:http://jsfiddle.net/tG9Qa/

答案 3 :(得分:1)

因此,对于后代:最简单的解决方案是让您的产品经理更改要求,以便您可以进行多行编辑。这就是我们案件中最终发生的事情。

然而,在此之前,我最终在创建一个手动移动的单行richtext编辑器方面走了很长一段路。我最后将它包装在一个jQuery插件中。我没时间完成它(IE中可能存在漏洞,Firefox效果最好,Chrome工作得很好 - 评论很少,有时不太清楚)。它使用部分Rangy library(因为我不想依赖完整的库而提取)来获取选择的屏幕位置,以便测试鼠标位置与选择(因此您可以拖动选择并移动框)。

粗略地说,它使用3个元素。一个外部div(你称之为插件的东西),它会溢出:隐藏,然后在其中嵌套2级。第一级是绝对定位,第二级是实际的满足。这种分离是必要的,因为否则一些浏览器会给出令人满意的绝对定位元素夹点,让用户移动它...

在任何情况下,都会有一大堆代码将绝对定位的元素移动到顶部元素内部,从而移动实际的contenteditable。 contenteditable本身有白色空间nowrap等,以迫使它保持一条线。

插件中还有代码可以从粘贴/拖入框中的任何文本中删除任何不是图像的内容(如br,表格等等)。你需要一些部分(比如brs,剥离/规范化段落等),但也许你通常希望保留链接,emstrong和/或其他一些格式。

来源:https://gist.github.com/1161922

答案 4 :(得分:1)

查看我刚刚发布的这个答案。这应该可以帮到你:

How to create a HTML5 single line contentEditable tab which listens to Enter and Esc

以下是HTML标记:

<span contenteditable="false"></span>

这是jQuery / javascript:

$(document).ready(function() {
    $('[contenteditable]').dblclick(function() {
        $(this).attr('contenteditable', 'true');
        clearSelection();
        $(this).trigger('focus');
    });

    $('[contenteditable]').live('focus', function() {
        before = $(this).text();
        if($(this).attr('contenteditable') == "true") { $(this).css('border', '1px solid #ffd646'); }
    //}).live('paste', function() {
    }).live('blur', function() {
        $(this).attr('contenteditable', 'false');
        $(this).css('border', '1px solid #fafafa');
        $(this).text($(this).text().replace(/(\r\n|\n|\r)/gm,""));

        if (before != $(this).text()) { $(this).trigger('change'); }
    }).live('keyup', function(event) {
        // ESC=27, Enter=13
        if (event.which == 27) {
            $(this).text(before);
            $(this).trigger('blur');
        } else if (event.which == 13) {
            $(this).trigger('blur');
        }
    });

    $('[contenteditable]').live('change', function() {
        var $thisText = $(this).text();
        //Do something with the new value.
    });
});

function clearSelection() {
    if ( document.selection ) {
        document.selection.empty();
    } else if ( window.getSelection ) {
        window.getSelection().removeAllRanges();
    }
}

希望这有助于某人!!!

答案 5 :(得分:1)

如果你想要一种不同的解决方法而不是改变要求,只需要display:table一点就完全可能=)

&#13;
&#13;
.container1 {
    height:20px;
    width:273px; 
    overflow:hidden;
    border:1px solid green;
}
.container2 {
    display:table;
}
.textarea {
    width:273px;
    font-size: 18px;
    font-weight: normal;
    line-height: 18px;
    outline: none;
    display: table-cell;
    position: relative;
    -webkit-user-select: text;
    -moz-user-select: text;
    -ms-user-select: text;
    user-select: text;
    word-wrap: break-word;    
    overflow:hidden;
}
&#13;
<div class="container1">
    <div class="container2">
        <div contenteditable="true" class="textarea"></div>
    </div>
</div>
&#13;
&#13;
&#13;

答案 6 :(得分:1)

我修改了@ tw16接受的解决方案(于2019年12月5日)以添加滚动。诀窍是使用removeEventListener添加滚动,然后隐藏滚动条(https://stackoverflow.com/a/49278385

overflow-x: auto
/* Existing Solution */
[contenteditable="true"].single-line {
    white-space: nowrap;
    width: 200px;
    overflow: hidden;
} 
[contenteditable="true"].single-line br {
    display:none;

}
[contenteditable="true"].single-line * {
    display:inline;
    white-space:nowrap;
}

/* Make Scrollable */
[contenteditable="true"].single-line {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none;  /* Internet Explorer 10+ */
}
[contenteditable="true"].single-line::-webkit-scrollbar { /* WebKit */
    width: 0;
    height: 0;
}    

答案 7 :(得分:0)

您可以使用文本输入替换此div(在调用onclick事件后) 我使用了类似于this plugin的东西,但效果很好。

答案 8 :(得分:0)

其他答案是错误的,几乎没有错误(于2019-05-07)。其他解决方案建议使用“空白:nowrap”(防止携带到另一行)+“溢出:隐藏”(防止长文本超出字段)+隐藏
等。

解决方案中的第一个错误是“溢出:隐藏”,这也阻止了滚动文本。用户将无法通过以下方式滚动文本:

  • 按下鼠标中键
  • 选择文本并将鼠标指针向左或向右移动
  • 使用水平鼠标滚动(当用户有这样的事情时)

他滚动的唯一方法是使用键盘箭头。

您可以通过同时使用“溢出:隐藏”和“溢出:自动”(或“滚动”)来解决此问题。您应使用“ overflow:hidden”创建父div,以隐藏用户不应看到的内容。该元素必须具有输入边框和其他设计。并且您应该使用“ overflow-x:auto”和“ contenteditable”属性创建子div。该元素将具有滚动条,因此用户可以不受限制地进行滚动,并且由于隐藏了父元素中的溢出,因此他不会看到此滚动条。

解决方案示例:

document.querySelectorAll('.CETextInput').forEach(el => {
	//Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
	el.parentNode.addEventListener('mousedown', function(e) {
		if (e.target === this) {
			setTimeout(() => this.children[0].focus(), 0);
		}
	});
	
	//Prevent Enter. See purpose in "Step 2" in answer.
	el.parentNode.addEventListener('keydown', function(e) {
		if (e.keyCode === 13)
			e.preventDefault();
	});
});
.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
	display: inline-block;
	border: 1px solid #aaa;
}

.CETextInputCont {
	overflow: hidden;
	cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
	
	/*Style:*/
	width: 10em;
	height: 1em;
	line-height: 1em;
	padding: 5px;
	font-size: 20px;
	font-family: sans-serif;
}

.CETextInput {
	white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
	overflow-x: auto;
	min-height: 100%; /*to prevent zero-height with no text*/
	
	/*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
	padding: 5px 0;
	margin-top: -5px;
	
	outline: none; /*Prevent border on focus in some browsers*/
}
<div class="CETextInputBorder">
	<div class="CETextInputCont">
		<div class="CETextInput" contenteditable></div>
	</div>
</div>


步骤2:解决
和其他问题:

还有一个问题,即用户或扩展名可以粘贴


  • (可由用户粘贴)
  • (可能很大)(可以由用户粘贴)
  • 具有另一个“空白”值的元素
  • 和其他将文本带到另一行的元素
  • 具有不合适的“显示”值的元素

但是建议隐藏全部
也是错误的。这是因为Mozilla Firefox在空白字段中添加了
元素(我想这可能是在删除最后一个字符后文本光标消失的错误的解决方法;已在2019-03-19发布的Firefox 66中进行了检查)。如果隐藏此元素,则当用户将焦点移至字段插入符号时,将在此隐藏的
元素中设置文本光标,并且文本光标也将始终隐藏。

如果您知道字段为空时将成为
,则可以解决此问题。您需要在此处使用一些JavaScript(您不能使用:empty选择器,因为field包含
元素且不能为空)。解决方案示例:

document.querySelectorAll('.CETextInput').forEach(el => {
	//OLD CODE:
	
	//Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
	el.parentNode.addEventListener('mousedown', function(e) {
		if (e.target === this) {
 	 	 	setTimeout(() => this.children[0].focus(), 0);
 	 	}
	});
	
	//Prevent Enter to prevent blur on Enter
	el.parentNode.addEventListener('keydown', function(e) {
		if (e.keyCode === 13)
			e.preventDefault();
	});
	
	//NEW CODE:
	
	//Update "empty" class on all "CETextInput" elements:
	updateEmpty.call(el); //init
	el.addEventListener('input', updateEmpty);

	function updateEmpty(e) {
		const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
		this.classList.toggle('empty', !s);
	}
});
/*OLD CODE:*/

.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
	display: inline-block;
	border: 1px solid #aaa;
}

.CETextInputCont {
	overflow: hidden;
	cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
	
	/*Style:*/
	width: 10em;
	height: 1em;
	line-height: 1em;
	padding: 5px;
	font-size: 20px;
	font-family: sans-serif;
}

.CETextInput {
	white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
	overflow-x: auto;
	min-height: 100%; /*to prevent zero-height with no text*/
	
	/*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
	padding: 5px 0;
	margin-top: -5px;
	
	outline: none; /*Prevent border on focus in some browsers*/
}

/*NEW CODE:*/

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
	display: none;
}

.CETextInput * {
	display: inline;
	white-space: pre;
}
<!--OLD CODE:-->

<div class="CETextInputBorder">
	<div class="CETextInputCont">
		<div class="CETextInput" contenteditable></div>
	</div>
</div>


第3步:解决获得价值的问题:

我们隐藏了
元素,因此“ innerText”值将不包含它们。但是:

  1. 设置“空”类时,结果可能包含
    元素。
  2. 您的其他样式或扩展名可能会通过“!important”标记或具有更高优先级的规则覆盖“ display:none”。

因此,当您获得价值时,应进行替换以避免意外的换行:

s = s.replace(/[\r\n]+/g, '');


请勿使用javascript隐藏

您还可以通过用javascript删除
来解决问题,但这是非常糟糕的解决方案,因为在每个删除用户之后,再也无法使用“撤消”操作来取消更改,而是在删除之前进行。

还可以使用document.execCommand('delete')删除
,但是很难实现+用户可以撤消删除并恢复
元素。


添加占位符

它没有被问到,但是我想许多使用单行contenteditable元素的人都需要它。这是示例如何使用我们在上面讨论过的css和“空”类制作占位符:

//OLD CODE:

document.querySelectorAll('.CETextInput').forEach(el => {
	//Focusing on child element after clicking parent. We need it because parent element has bigger width than child.
	el.parentNode.addEventListener('mousedown', function(e) {
		if (e.target === this) {
 	 	 	setTimeout(() => this.children[0].focus(), 0);
 	 	}
	});
	
	//Prevent Enter to prevent blur on Enter
	el.parentNode.addEventListener('keydown', function(e) {
		if (e.keyCode === 13)
			e.preventDefault();
	});
	
	//Update "empty" class on all "CETextInput" elements:
	updateEmpty.call(el); //init
	el.addEventListener('input', updateEmpty);

	function updateEmpty(e) {
		const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
		this.classList.toggle('empty', !s);
		
		//NEW CODE:
		
		//Make element always have <br>. See description in html. I guess it is not needed because only Firefox has bug with bad cursor position but Firefox always adds this element by itself except on init. But on init we are adding it by ourselves (see html).
		if (!s && !Array.prototype.filter.call(this.children, el => el.nodeName === 'BR').length)
			this.appendChild(document.createElement('br'));
	}
});
/*OLD CODE:*/

.CETextInputBorder { /*This element is needed to prevent cursor: text on border*/
	display: inline-block;
	border: 1px solid #aaa;
}

.CETextInputCont {
	overflow: hidden;
	cursor: text; /*You must set it because parent elements is bigger then child contenteditable element. Also you must add javascript to focus child element on click parent*/
	
	/*Style:*/
	width: 10em;
	height: 1em;
	line-height: 1em;
	padding: 5px;
	font-size: 20px;
	font-family: sans-serif;
}

.CETextInput {
	white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
	overflow-x: auto;
	min-height: 100%; /*to prevent zero-height with no text*/
	
	/*We will duplicate vertical padding to let user click contenteditable element on top and bottom. We would do same thing for horizontal padding but it is not working properly (in all browsers when scroll is in middle position and in Firefox when scroll is at the end). You can also replace vertical padding with just bigger line height.*/
	padding: 5px 0;
	margin-top: -5px;
	
	outline: none; /*Prevent border on focus in some browsers*/
}

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
	display: none;
}

.CETextInput * {
	display: inline;
	white-space: pre;
}

/*NEW CODE:*/

.CETextInput[placeholder].empty::before { /*Use ::before not ::after or you will have problems width first <br>*/
	content: attr(placeholder);
	display: inline-block;
	width: 0;
	white-space: nowrap;
	pointer-events: none;
	cursor: text;
	color: #b7b7b7;
	
	padding-top: 8px;
	margin-top: -8px;
}
<!--OLD CODE:-->

<div class="CETextInputBorder">
	<div class="CETextInputCont">
		<div class="CETextInput" placeholder="Type something here" contenteditable><br></div>
	</div>
</div>

<!--We manually added <br> element for Firefox browser because Firefox (tested on 2019-05-11, Firefox 66) has bug with bad text cursor position in empty contenteditable elements that have ::before or ::after pseudo-elements.-->


只有一个div和“ scrollbar-width”的解决方案

通过设置“ overflow-x:自动”,“ overflow-y:隐藏”和“ scrollbar-width:无”,您也只能使用一个div。但是“ scrollbar-width”是新属性,仅在Firefox 64+及更高版本中无法使用。

您还可以添加:

  • webkit前缀版本:“-webkit-scrollbar-width:无”
  • 非标准化的“ .CETextInput ::-webkit-scrollbar {display:none;}”(对于基于Webkit的浏览器)
  • “-ms-overflow-style:none”

我不建议使用此解决方案,但这是示例:

//OLD CODE:

document.querySelectorAll('.CETextInput').forEach(el => {
	//Focusing on child is not needed anymore
	
	//Prevent Enter to prevent blur on Enter
	el.addEventListener('keydown', function(e) {
		if (e.keyCode === 13)
			e.preventDefault();
	});
	
	//Update "empty" class on all "CETextInput" elements:
	updateEmpty.call(el); //init
	el.addEventListener('input', updateEmpty);

	function updateEmpty(e) {
		const s = this.innerText.replace(/[\r\n]+/g, ''); //You must use this replace, see explanation below in "Step 3"
		this.classList.toggle('empty', !s);
	}
});
/*NEW CODE:*/

.CETextInput {
	white-space: pre; /*"pre" is like "nowrap" but displays all spaces correctly (with "nowrap" last space is not displayed in Firefox, tested on Firefox 66, 2019-05-15)*/
	overflow-x: auto; /*or "scroll"*/
	overflow-y: hidden;
	-webkit-scrollbar-width: none; /*Chrome 4+ (probably), webkit based*/
	scrollbar-width: none; /*FF 64+, Chrome ??+, webkit based, Edge ??+*/
	-ms-overflow-style: none; /*IE ??*/
	
	/*Style:*/
	width: 10em;
	height: 1em;
	line-height: 1em;
	padding: 5px;
	border: 1px solid #aaa;
	font-size: 20px;
	font-family: sans-serif;
}

.CETextInput::-webkit-scrollbar {
	display: none; /*Chrome ??, webkit based*/
}

/*OLD CODE:*/

.CETextInput:not(.empty) br,
.CETextInput img { /*We hide <img> here. If you need images do not hide them but set maximum height. User can paste image by pressing Ctrl+V or Ctrl+Insert.*/
	display: none;
}

.CETextInput * {
	display: inline;
	white-space: pre;
}
<!--NEW CODE:-->

<div class="CETextInput" contenteditable></div>

此解决方案在填充方面存在 3个问题

  1. 在Firefox(经过测试,于2019-05-11,Firefox 66)中,键入长文本时没有右填充。这是因为当在具有滚动条的同一元素中使用填充以及将内容滚动到末尾时,Firefox不会显示底部或右侧填充。
  2. 在所有浏览器中,在中间位置滚动长文本时都不会填充。看起来更糟没有这个问题。
  3. 当用户按下主屏幕或最终浏览器时,滚动到不显示填充的位置。

要解决这些问题,您需要使用我们之前使用的3个元素,但是在这种情况下,您不需要使用scrollbar-width。我们的三要素解决方案没有这些问题。


其他问题(在每种解决方案中):

  • 模糊粘贴文本以换行符结尾。我会考虑如何解决。
  • 在使用填充时,在基于Webkit的浏览器中,this.children [0] .focus()不够(光标位置不在用户单击的位置)。我会考虑如何解决。
  • Firefox(已于2019-05-11,Firefox 66测试):键入短文本时,用户无法通过双击其右侧来选择最后一个单词。我会考虑的。
  • 当用户在页面中开始文本选择时,他可以在我们的字段中结束文本选择。通常没有此行为。但是我不认为这很关键。

答案 9 :(得分:-3)

使用jQuery我已经设置了一个.keypress事件,然后测试了e.keyCode == 13(返回键),如果从事件返回false并且编辑无法创建多行

$('*[contenteditable=yes]').keypress(function(e) {
  if(e.keyCode == 13 && !$(this).data('multiline')) {
    return false;
  }
})