繁体   English   中英

可编辑的单行输入

[英]contenteditable single-line input

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

我们原来的计划是使用contenteditable <div> 我们为粘贴事件以及不同的键/鼠标交互使用侦听器,以确保没有不需要的标记进入 contenteditable(我们从容器标签中去除文本,只留下我们自己插入的图像标签)。

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

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

如果有一个我遗漏的相对简单的属性/css 属性可以做我想做的事情,我会最好,但如果有必要,CSS+JS 建议也将受到赞赏。

 [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>​

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

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

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

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

您可以通过同时使用“溢出:隐藏”和“溢出:自动”(或“滚动”)来解决此问题。 您应该使用“溢出:隐藏”创建父 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 步:解决 <br> 和其他问题:

还有一个问题是用户或扩展程序可以粘贴

  • <br>(可由用户粘贴)
  • <img>(可能有大尺寸)(可由用户粘贴)
  • 具有另一个“空白”值的元素
  • <div> 和其他将文本传送到另一行的元素
  • 具有不合适“显示”值的元素

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

如果您在知道字段为空时将成为 <br>,则可以解决此问题。 你在这里需要一些 javascript(你不能使用 :empty 选择器,因为字段包含 <br> 元素而不是空的)。 解决方案示例:

 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 步:解决获取价值的问题:

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

  1. 设置“空”类时,结果可能包含 <br> 元素。
  2. 您的其他样式或扩展可能会通过“!重要”标记或具有更高优先级的规则覆盖“显示:无”。

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

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


不要使用 javascript 来隐藏 <br>

您也可以通过用 javascript 删除它们来解决 <br> 的问题,但这是非常糟糕的解决方案,因为在每个删除用户之后不能再使用“撤消”操作来取消在删除之前所做的更改。

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


添加占位符

它没有被问到,但我想很多使用单行 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 和“滚动条宽度”的解决方案

你也可以通过设置“overflow-x: auto”、“overflow-y: hidden”和“scrollbar-width: none”来只使用一个div。 但“滚动条宽度”是新属性,仅适用于 Firefox 64+,尚不适用于其他浏览器。

您还可以添加:

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

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

 //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 个关于 padding 的问题

  1. 在 Firefox 中(在 2019-05-11 上测试,Firefox 66)在输入长文本时没有正确的填充。 这是因为在具有滚动条的同一元素中使用填充以及内容滚动到末尾时,Firefox 不会显示底部或右侧填充。
  2. 在所有浏览器中,在中间位置滚动长文本时没有填充。 看起来更糟。 <input type="text"> 没有这个问题。
  3. 当用户按主页或结束浏览器滚动以放置填充不可见时。

要解决这些问题,您需要使用我们之前使用的 3 个元素,但在这种情况下,您不需要使用滚动条宽度。 我们的 3 个元素的解决方案没有这些问题。


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

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

我认为您正在寻找一个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 中不起作用(尽管如果需要,您可以使用条件注释来应用单独的样式)。

这是一个相对简单的解决方案,它使用 contenteditable 的输入事件来扫描 dom 并过滤掉各种风格的新行(因此它应该对复制/粘贴、拖放、在键盘上按 Enter 键等具有鲁棒性)。 它将多个 TextNode 压缩为单个 TextNode,从 TextNode 中去除换行符,杀死 BR,并在它接触的任何其他元素上放置一个“显示:内联”。 在 Chrome 上测试,在其他任何地方都不能保证。

 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; }
 #editable { width: 200px; height: 20px; border: 1px solid black; }
 <div id="editable" contenteditable="true"></div>

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

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

 /* 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; }
 <div contenteditable="true" class="single-line"> This should scroll when you have really long text! </div>​

如果你想用不同的方式来解决它而不是改变需求,用一点display:table完全有可能 =)

 .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; }
 <div class="container1"> <div class="container2"> <div contenteditable="true" class="textarea"></div> </div> </div>

看看我刚刚发布的这个答案。 这应该可以帮助您:

如何创建一个 HTML5 单行 contentEditable 选项卡来监听 Enter 和 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();
    }
}

希望这对某人有所帮助!!!

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

然而,在那之前,我最终在创建一个手动移动的单行富文本编辑器方面做了很多努力。 最后我将它封装在一个 jQuery 插件中。 我没有时间完成它(IE 中可能存在错误,Firefox 工作得最好,Chrome 工作得很好——评论很少,有时不是很清楚)。 它使用部分Rangy 库(因为我不想依赖完整库而提取)来获取选择的屏幕位置,以测试鼠标位置与选择(因此您可以拖动选择并移动框)。

粗略地说,它通过使用 3 个元素来工作。 一个外部 div(你调用插件的东西),它会溢出:隐藏,然后在里面有 2 层嵌套。 第一级是绝对定位,第二级是实际内容编辑。 这种分离是必要的,否则一些浏览器会给 contenteditable 绝对定位的元素提供抓地力,让用户移动它......

在任何情况下,然后有一大堆代码来移动顶部元素内的绝对定位元素,从而移动实际的 contenteditable。 contenteditable 本身有空格 nowrap 等,以强制它保持一行。

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

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

您可以将此 div 替换为文本输入(在调用 onclick 事件之后)。
我使用了类似于这个插件的东西,它工作得很好。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM