web-dev-qa-db-ja.com

コンテンツ編集可能な単一行入力

私が働いている会社で開発しているアプリケーションの場合、JSベースのWebアプリ内に絵文字を挿入することをサポートする入力が必要です。現在、エモーティコンショートコード(「:-)」など)を使用して入力を使用しており、実際のグラフィックイメージの挿入に切り替えたいと考えています。

元の計画では、contenteditable<div>。 pasteイベントと異なるキー/マウスインタラクションのリスナーを使用して、不要なマークアップがcontenteditableに入らないようにします(コンテナタグからテキストを取り除き、自分で挿入したイメージタグのみを残します)。

ただし、現在の問題は、十分なコンテンツを入れると(つまり、高さが高くなると)divのサイズが変更されることです。これが発生することは望ましくありません。また、テキストを非表示にするだけでもかまいません(つまり、プレーンoverflow: hidden)。そう:

contenteditable divを単一行の入力のように動作させる方法はありますか?

私が見逃した比較的単純な属性/ cssプロパティがあればそれが一番欲しいのですが、必要であればCSS + JSの提案も感謝されます。

53
Gijs
[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>​
103
Alessio

contenteditableをオーバーフローしたときに水平方向にスクロールする1行のテキストのみを含むdivdivを探していると思います。これはトリックを行う必要があります: 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では機能しません(ただし、必要に応じて条件付きコメントを使用して個別のスタイルを適用できます)。

6
tw16

Contenteditableの入力イベントを使用してdomをスキャンし、さまざまなフレーバーの新しい行を除外する比較的単純なソリューションを次に示します(したがって、コピー/貼り付け、ドラッグアンドドロップ、キーボードのEnterキーを押すなどに対して堅牢である必要があります)。複数のTextNodeを1つのTextNodeに凝縮し、TextNodeから改行を取り除き、BRを削除し、接触する他の要素に「display:inline」を配置します。 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/

3
josh

要件を変更する以外に別の方法で解決したい場合は、少し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>
2
Berge Aadland

したがって、後世のために:最も簡単な解決策は、製品マネージャーに要件を変更させて、複数行の編集を行えるようにすることです。これが私たちの場合に起こったことです。

しかし、それが起こる前に、私は手動で移動する単一行のリッチテキストエディターを作成することにかなりの方法をとることになりました。最後にjQueryプラグインにまとめました。私はそれを終わらせる時間がありません(おそらくIEにバグがあり、Firefoxは最適に動作し、Chromeは非常に良好に動作します-コメントはまばらで、あまり明確ではありません)。 Rangyライブラリ (完全なライブラリに依存したくなかったため抽出)マウス位置と選択をテストするために、選択の画面位置を取得します(選択をドラッグして、ボックス)。

大体、3つの要素を使用して機能します。オーバーフローが発生する外側のdiv(プラグインを呼び出すもの):隠し、その中に2レベルのネスト。最初のレベルは絶対に配置され、2番目のレベルは実際のコンテンツ編集可能です。それ以外の場合、一部のブラウザはコンテンツ編集可能な絶対配置要素グリッピーを提供し、ユーザーが移動できるようにするため...

いずれの場合でも、絶対位置にある要素を最上位要素内で移動するためのコード全体があり、実際のコンテンツを編集可能にします。 contenteditable自体には、1行のままにするための空白のnowrapなどがあります。

プラグインには、ボックスに貼り付け/ドラッグされたテキストから画像ではないもの(br、テーブルなど)を取り除くコードもあります。これにはいくつかの部分(brs、段落の除去/正規化など)が必要ですが、通常はリンク、emstrong、および/またはその他のフォーマットを保持する必要があります。

ソース: https://Gist.github.com/1161922

1
Gijs

他の回答は間違っており、いくつかの間違いが含まれています(2019-05-07)。他のソリューションでは、「white-space:nowrap」(別の行への移動を防ぐ)+「overflow:hidden」(長いテキストがフィールドを超えて進むのを防ぐ)+非表示<br>などを使用することをお勧めします。

その解決策の最初の間違いは、「オーバーフロー:隠された」テキストのスクロールを妨げることです。ユーザーは次の方法でテキストをスクロールできません。

  • マウスの中央ボタンを押す
  • テキストを選択し、マウスポインターを左右に移動する
  • 水平マウススクロールの使用(ユーザーがそのようなことをしている場合)

彼がスクロールできる唯一の方法は、キーボードの矢印を使用することです。

この問題は、「overflow:hidden」と「overflow:auto」(または「scroll」)を同時に使用することで解決できます。 「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:<br>およびその他の問題を解決する:

また、ユーザーまたは拡張機能が貼り付けることができるという問題があります

  • <br>(ユーザーが貼り付けることができます)
  • <img>(サイズが大きい場合があります)(ユーザーが貼り付けることができます)
  • 別の「空白」値を持つ要素
  • <div>およびテキストを別の行に運ぶその他の要素
  • 不適切な「表示」値を持つ要素

しかし、非表示にすることをお勧めします すべて <br>も間違っています。これは、Mozilla Firefoxが空のフィールドに<br>要素を追加するためです(最後の文字を削除するとテキストカーソルが消えるバグの回避策であると思われます。2019-03-19にリリースされたFirefox 66で確認されています)。この要素を非表示にすると、ユーザーがフィールドにフォーカスを移動すると、この非表示<br>要素にキャレットが設定され、テキストカーソルも(常に)非表示になります。

フィールドが空であることがわかっているときに<br>になる場合は、これを修正できます。ここにはJavaScriptが必要です(フィールドには空ではない<br>要素が含まれているため、:emptyセレクターは使用できません)。ソリューションの例:

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」値に含まれないように、<br>要素を非表示にしました。しかし:

  1. 「空の」クラスが設定されている場合、結果には<br>要素が含まれる場合があります。
  2. 他のスタイルまたは拡張機能は、「!important」マークまたは優先度の高いルールによって「display:none」をオーバーライドする場合があります。

したがって、値を取得するときは、誤って改行が入らないように置換する必要があります。

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


非表示にJavaScriptを使用しない<br>

また、JavaScriptでそれらを削除することで<br>の問題を解決することもできますが、これは非常に悪い解決策です。

また、document.execCommand( 'delete')を使用して<br>を削除することもできますが、実装が難しく、ユーザーは削除を取り消して<br>要素を復元できます。


プレースホルダーを追加する

質問ではありませんでしたが、単一行のコンテンツ編集可能な要素を使用する多くの人に必要になると思います。上記の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"が1つだけのソリューション

「overflow-x:auto」、「overflow-y:hidden」、「scrollbar-width:none」を設定して、1つのdivのみを使用することもできます。ただし、「scrollbar-width」は新しいプロパティであり、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つの問題 パディング付き:

  1. Firefox(2019-05-11、Firefox 66でテスト済み)では、長いテキストが入力されたときに正しいパディングがありません。これは、スクロールバーを持つ同じ要素でパディングを使用する場合、およびコンテンツが最後までスクロールされる場合、Firefoxは下または右のパディングを表示しないためです。
  2. すべてのブラウザで、中央の位置で長いテキストをスクロールするときにパディングはありません。悪く見えます。 <input type = "text">にはこの問題はありません。
  3. ユーザーがホームを押すかブラウザを終了すると、パディングが表示されないようにスクロールします。

これらの問題を解決するには、以前使用したような3つの要素を使用する必要がありますが、この場合はscrollbar-widthを使用する必要はありません。 3つの要素を持つソリューションには、これらの問題はありません。


その他の問題(すべてのソリューション):

  • テキストの貼り付けのぼかしは改行で終了します。私はそれを修正する方法を考えます。
  • パディングを使用する場合、this.children [0] .focus()はWebkitベースのブラウザーでは十分ではありません(カーソルの位置はユーザーがクリックした場所ではありません)。私はそれを修正する方法を考えます。
  • Firefox(2019-05-11、Firefox 66でテスト済み):短いテキストが入力された場合、ユーザーは右側のダブルクリックで最後のWordを選択できません。私はそれについて考えるだろう。
  • ユーザーがページでテキストの選択を開始すると、フィールドで終了できます。通常の<input type = "text">にはこの動作はありません。しかし、私はそれが重要だとは思わない。
1
vitaliydev

先ほど投稿したこの回答をご覧ください。これはあなたを助けるはずです:

EnterとEscをリッスンするHTML5単一行contentEditableタブの作成方法

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();
    }
}

これが誰かを助けることを願っています!!!

1
trgraglia

このdivをテキスト入力に置き換えることができます(onclickイベントが呼び出された後)。
このプラグイン に似たものを使用しましたが、正常に機能しました。

0
shaggy