web-dev-qa-db-ja.com

Textareaのカーソル位置にDIVを表示

私のプロジェクトでは、特定のテキストエリアにオートコンプリートを提供したいと思います。 intellisense/omnicompleteのしくみに似ています。ただし、そのためには、DIVが表示される場所がわかるように、絶対カーソル位置を見つける必要があります。

結局のところ、それは(ほとんど私が望んでいる)達成することは不可能です。誰もがその問題を解決するためのいくつかのきちんとしたアイデアを持っていますか?

58
Armin Ronacher

私のハッキー実験のバージョン2

この新しいバージョンは、必要に応じて調整できる任意のフォント、および任意のtextareaサイズで動作します。

一部の人がまだこれを機能させようとしていることに気づいた後、新しいアプローチを試すことにしました。私の結果は、今のところずっと良くなっています-少なくともgoogle chromeです。WindowsPCが利用できなくなったので、chrome/Ubuntuでのfirefox。結果はChromeで100%一貫して機能し、Firefoxで70〜80%程度だと思いますが、不整合を見つけるのが信じられないほど難しいとは思いません。

この新しいバージョンはCanvasオブジェクトに依存しています。私の example では、実際にそのキャンバスを示しています。実際にそれを見ることができますが、非表示のキャンバスオブジェクトを使用すると非常に簡単に行うことができます。

これは間違いなくハックであり、私はむしろ一緒に投げられたコードを前もって謝罪します。少なくとも、Google Chromeでは、設定したフォントやtextareaのサイズに関係なく、一貫して機能します。私は Sam Saffron の例を使用してカーソル座標(灰色の背景のdiv)を表示しました。 「ランダム化」リンクも追加したので、さまざまなフォント/ texareaのサイズとスタイルで機能することを確認し、その場でカーソル位置の更新を確認できます。 ページ全体のデモ をご覧になることをお勧めします。これにより、コンパニオンキャンバスの動きがよくわかるようになります。

それがどのように機能するかを要約します...

根底にある考えは、キャンバス上のテキストエリアを可能な限り厳密に再描画しようとしているということです。ブラウザーはとtexareaの両方に同じフォントエンジンを使用しているため、キャンバスのフォント測定機能を使用して、どこにあるかを把握できます。そこから、座標を把握するために使用できるキャンバスメソッドを使用できます。

まず何よりも、textareaのサイズに合わせてキャンバスを調整します。キャンバスのサイズは実際の結果に影響しないため、これは完全に視覚的な目的のためです。 Canvasは実際にはワードラップの手段を提供していないので、テキストエリアに可能な限り一致するように行を分割する手段を想起(スチール/借用/変更)する必要がありました。これは、最もブラウザー間の微調整を行う必要があると思われる場所です。

ワードラップ後、その他はすべて基本的な数学です。行を配列に分割してWordの折り返しを模倣します。次に、これらの行をループして、現在の選択が終了するところまで下に移動します。これを行うには、文字を数えるだけで、selection.endを超えると、十分に下がったことがわかります。その点までのラインカウントをline-heightで乗算すると、y座標が得られます。

x座標は、context.measureTextを使用していることを除いて、非常によく似ています。適切な数の文字を出力している限り、Canvasに描画されている線の幅がわかります。これは、最後に書き出された文字(現在の文字の前の文字)の後で終了しますselection.end ポジション。

他のブラウザでこれをデバッグしようとするとき、探すべきことは、行が適切に壊れないところです。いくつかの場所で、キャンバスの行の最後のWordがtextareaで折り返されているか、またはその逆があることがわかります。これは、ブラウザがワードラップを処理する方法に関係しています。キャンバスのラッピングがtextareaに一致する限り、カーソルは正しいはずです。

以下のソースを貼り付けます。あなたはそれをコピーして貼り付けることができるはずですが、もしそうなら、私は私のサーバーにあるjquery-fieldselectionを押す代わりに、あなた自身のjquery-fieldselectionのコピーをダウンロードするようお願いします。

新しいデモフィドル もアップしました。

幸運を!

<!DOCTYPE html>
<html lang="en-US">
    <head>
        <meta charset="utf-8" />
        <title>Tooltip 2</title>
        <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
        <script type="text/javascript" src="http://enobrev.info/cursor/js/jquery-fieldselection.js"></script>
        <style type="text/css">
            form {
                float: left;
                margin: 20px;
            }

            #textariffic {
                height: 400px;
                width: 300px;
                font-size: 12px;
                font-family: 'Arial';
                line-height: 12px;
            }

            #tip {
                width:5px;
                height:30px;
                background-color: #777;
                position: absolute;
                z-index:10000
            }

            #mock-text {
                float: left;
                margin: 20px;
                border: 1px inset #ccc;
            }

            /* way the hell off screen */
            .scrollbar-measure {
                width: 100px;
                height: 100px;
                overflow: scroll;
                position: absolute;
                top: -9999px;
            }

            #randomize {
                float: left;
                display: block;
            }
        </style>
        <script type="text/javascript">
            var oCanvas;
            var oTextArea;
            var $oTextArea;
            var iScrollWidth;

            $(function() {
                iScrollWidth = scrollMeasure();
                oCanvas      = document.getElementById('mock-text');
                oTextArea    = document.getElementById('textariffic');
                $oTextArea   = $(oTextArea);

                $oTextArea
                        .keyup(update)
                        .mouseup(update)
                        .scroll(update);

                $('#randomize').bind('click', randomize);

                update();
            });

            function randomize() {
                var aFonts      = ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Impact', 'Times New Roman', 'Verdana', 'Webdings'];
                var iFont       = Math.floor(Math.random() * aFonts.length);
                var iWidth      = Math.floor(Math.random() * 500) + 300;
                var iHeight     = Math.floor(Math.random() * 500) + 300;
                var iFontSize   = Math.floor(Math.random() * 18)  + 10;
                var iLineHeight = Math.floor(Math.random() * 18)  + 10;

                var oCSS = {
                    'font-family':  aFonts[iFont],
                    width:          iWidth + 'px',
                    height:         iHeight + 'px',
                    'font-size':    iFontSize + 'px',
                    'line-height':  iLineHeight + 'px'
                };

                console.log(oCSS);

                $oTextArea.css(oCSS);

                update();
                return false;
            }

            function showTip(x, y) {
                $('#tip').css({
                      left: x + 'px',
                      top: y + 'px'
                  });
            }

            // https://stackoverflow.com/a/11124580/14651
            // https://stackoverflow.com/a/3960916/14651

            function wordWrap(oContext, text, maxWidth) {
                var aSplit = text.split(' ');
                var aLines = [];
                var sLine  = "";

                // Split words by newlines
                var aWords = [];
                for (var i in aSplit) {
                    var aWord = aSplit[i].split('\n');
                    if (aWord.length > 1) {
                        for (var j in aWord) {
                            aWords.Push(aWord[j]);
                            aWords.Push("\n");
                        }

                        aWords.pop();
                    } else {
                        aWords.Push(aSplit[i]);
                    }
                }

                while (aWords.length > 0) {
                    var sWord = aWords[0];
                    if (sWord == "\n") {
                        aLines.Push(sLine);
                        aWords.shift();
                        sLine = "";
                    } else {
                        // Break up work longer than max width
                        var iItemWidth = oContext.measureText(sWord).width;
                        if (iItemWidth > maxWidth) {
                            var sContinuous = '';
                            var iWidth = 0;
                            while (iWidth <= maxWidth) {
                                var sNextLetter = sWord.substring(0, 1);
                                var iNextWidth  = oContext.measureText(sContinuous + sNextLetter).width;
                                if (iNextWidth <= maxWidth) {
                                    sContinuous += sNextLetter;
                                    sWord = sWord.substring(1);
                                }
                                iWidth = iNextWidth;
                            }
                            aWords.unshift(sContinuous);
                        }

                        // Extra space after Word for mozilla and ie
                        var sWithSpace = (jQuery.browser.mozilla || jQuery.browser.msie) ? ' ' : '';
                        var iNewLineWidth = oContext.measureText(sLine + sWord + sWithSpace).width;
                        if (iNewLineWidth <= maxWidth) {  // Word fits on current line to add it and carry on
                            sLine += aWords.shift() + " ";
                        } else {
                            aLines.Push(sLine);
                            sLine = "";
                        }

                        if (aWords.length === 0) {
                            aLines.Push(sLine);
                        }
                    }
                }
                return aLines;
            }

            // http://davidwalsh.name/detect-scrollbar-width
            function scrollMeasure() {
                // Create the measurement node
                var scrollDiv = document.createElement("div");
                scrollDiv.className = "scrollbar-measure";
                document.body.appendChild(scrollDiv);

                // Get the scrollbar width
                var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;

                // Delete the DIV
                document.body.removeChild(scrollDiv);

                return scrollbarWidth;
            }

            function update() {
                var oPosition  = $oTextArea.position();
                var sContent   = $oTextArea.val();
                var oSelection = $oTextArea.getSelection();

                oCanvas.width  = $oTextArea.width();
                oCanvas.height = $oTextArea.height();

                var oContext    = oCanvas.getContext("2d");
                var sFontSize   = $oTextArea.css('font-size');
                var sLineHeight = $oTextArea.css('line-height');
                var fontSize    = parseFloat(sFontSize.replace(/[^0-9.]/g, ''));
                var lineHeight  = parseFloat(sLineHeight.replace(/[^0-9.]/g, ''));
                var sFont       = [$oTextArea.css('font-weight'), sFontSize + '/' + sLineHeight, $oTextArea.css('font-family')].join(' ');

                var iSubtractScrollWidth = oTextArea.clientHeight < oTextArea.scrollHeight ? iScrollWidth : 0;

                oContext.save();
                oContext.clearRect(0, 0, oCanvas.width, oCanvas.height);
                oContext.font = sFont;
                var aLines = wordWrap(oContext, sContent, oCanvas.width - iSubtractScrollWidth);

                var x = 0;
                var y = 0;
                var iGoal = oSelection.end;
                aLines.forEach(function(sLine, i) {
                    if (iGoal > 0) {
                        oContext.fillText(sLine.substring(0, iGoal), 0, (i + 1) * lineHeight);

                        x = oContext.measureText(sLine.substring(0, iGoal + 1)).width;
                        y = i * lineHeight - oTextArea.scrollTop;

                        var iLineLength = sLine.length;
                        if (iLineLength == 0) {
                            iLineLength = 1;
                        }

                        iGoal -= iLineLength;
                    } else {
                        // after
                    }
                });
                oContext.restore();

                showTip(oPosition.left + x, oPosition.top + y);
            }

        </script>
    </head>
    <body>

        <a href="#" id="randomize">Randomize</a>

        <form id="tipper">
            <textarea id="textariffic">Aliquam urna. Nullam augue dolor, tincidunt condimentum, malesuada quis, ultrices at, arcu. Aliquam nunc pede, convallis auctor, sodales eget, aliquam eget, ligula. Proin nisi lacus, scelerisque nec, aliquam vel, dictum mattis, eros. Curabitur et neque. Fusce sollicitudin. Quisque at risus. Suspendisse potenti. Mauris nisi. Sed sed enim nec dui viverra congue. Phasellus velit sapien, porttitor vitae, blandit volutpat, interdum vel, enim. Cras sagittis bibendum neque. Proin eu est. Fusce arcu. Aliquam elit nisi, malesuada eget, dignissim sed, ultricies vel, purus. Maecenas accumsan diam id nisi.

Phasellus et nunc. Vivamus sem felis, dignissim non, lacinia id, accumsan quis, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed scelerisque nulla sit amet mi. Nulla consequat, elit vitae tempus vulputate, sem libero rhoncus leo, vulputate viverra nulla purus nec turpis. Nam turpis sem, tincidunt non, congue lobortis, fermentum a, ipsum. Nulla facilisi. Aenean facilisis. Maecenas a quam eu nibh lacinia ultricies. Morbi malesuada orci quis tellus.

Sed eu leo. Donec in turpis. Donec non neque nec ante tincidunt posuere. Pellentesque blandit. Ut vehicula vestibulum risus. Maecenas commodo placerat est. Integer massa nunc, luctus at, accumsan non, pulvinar sed, odio. Pellentesque eget libero iaculis dui iaculis vehicula. Curabitur quis nulla vel felis ullamcorper varius. Sed suscipit pulvinar lectus.</textarea>

        </form>

        <div id="tip"></div>

        <canvas id="mock-text"></canvas>
    </body>
</html>

バグ

覚えているバグが1つあります。カーソルを行の最初の文字の前に置くと、「位置」が前の行の最後の文字として表示されます。これは、selection.endの動作に関係しています。そのケースを探してそれに応じて修正することはそれほど難しくないと思います。


バージョン1

これをそのままにしておくと、編集履歴を調べなくても進行状況を確認できます。

それは完璧ではなく、間違いなくハックですが、WinXP IE、FF、Safari、ChromeおよびOperaでうまく機能するようになりました。

私が知る限り、どのブラウザでもカーソルのx/yを直接確認する方法はありません。 IEメソッド言及されたAdam Bellaire は興味深いですが、残念ながらクロスブラウザではありません。次に良いのはキャラクターをグリッドとして使うことだと思いました。

残念ながら、どのブラウザにもフォントメトリック情報が組み込まれていません。つまり、等幅フォントは、一貫した測定を行う唯一のフォントタイプです。また、font-heightからfont-widthを計算する信頼できる方法はありません。最初は、高さのパーセンテージを使用してみましたが、これはうまくいきました。それから私はフォントサイズを変更し、すべてが地獄に行きました。

一時的なテキストエリアを作成し、scrollHeight(またはscrollWidth)が変更されるまで文字を追加し続けるという方法で、文字の幅を把握する1つの方法を試しました。それはもっともらしいようですが、その道の半分ほどのところで、私はtextareaでcols属性を使用するだけで十分だと気づき、この試練には別のハックが十分にあると考えました。これは、cssを介してtextareaの幅を設定できないことを意味します。これを機能させるには、colsを使用する必要があります。

次に遭遇した問題は、cssを使用してフォントを設定した場合でも、ブラウザーがフォントを異なる方法で報告することです。フォントを設定しない場合、Mozillaはデフォルトでmonospaceを使用し、IEはCourier Newを使用します、Opera "Courier New"(引用符付き)、Safari、'Lucida Grand'(単一引用符付き)。フォントをmonospaceに設定すると、mozillaで、つまり、指定したものを使用すると、Safariは-webkit-monospaceおよびOperaは"Courier New"のままです。

そこで、いくつかの変数を初期化します。 CSSでも行の高さを設定してください。 Firefoxは正しい行の高さを報告しますが、IEは「通常」を報告しており、他のブラウザーでは気にしませんでした。CSSで行の高さを設定するだけで、違いが解決しました。私はピクセルの代わりにemsを使用してテストしていません。文字の高さは単なるフォントサイズです。おそらくCSSでも事前に設定しておく必要があります。

また、キャラクターを配置する前のもう1つの事前設定-本当に頭を掻きました。 ieとmozillaの場合、texarea文字は<colsで、それ以外はすべて<= charsです。したがって、Chromeは50文字を収めることができますが、mozillaおよびieは最後のWordを行から切り離します。

次に、すべての行の最初の文字の位置の配列を作成します。テキストエリアのすべての文字をループします。改行の場合は、行配列に新しい位置を追加します。スペースの場合、現在の「Word」が現在の行に収まるか、次の行にプッシュされるかを判断しようとします。句読点は「単語」の一部としてカウントされます。私はタブでテストしていませんが、タブ文字に4文字を追加するための行があります。

行の位置の配列を取得したら、ループしてカーソルが置かれている行を探します。選択範囲の「終了」をカーソルとして使用しています。

x =(カーソル位置-カーソル行の最初の文字位置)*文字幅

y =((カーソル行+ 1)*行の高さ)-スクロール位置

jquery 1.2.6jquery-fieldselection 、および jquery-dimensions を使用しています

デモ: http://enobrev.info/cursor/

そしてコード:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Tooltip</title>
        <script type="text/javascript" src="js/jquery-1.2.6.js"></script>
        <script type="text/javascript" src="js/jquery-fieldselection.js"></script>
        <script type="text/javascript" src="js/jquery.dimensions.js"></script>
        <style type="text/css">
            form {
                margin: 20px auto;
                width: 500px;
            }

            #textariffic {
                height: 400px;
                font-size: 12px;
                font-family: monospace;
                line-height: 15px;
            }

            #tip {
                position: absolute;
                z-index: 2;
                padding: 20px;
                border: 1px solid #000;
                background-color: #FFF;
            }
        </style>
        <script type="text/javascript">
            $(function() {
                $('textarea')
                    .keyup(update)
                    .mouseup(update)
                    .scroll(update);
            });

            function showTip(x, y) {                
                y = y + $('#tip').height();

                $('#tip').css({
                    left: x + 'px',
                    top: y + 'px'
                });
            }

            function update() {
                var oPosition = $(this).position();
                var sContent = $(this).val();

                var bGTE = jQuery.browser.mozilla || jQuery.browser.msie;

                if ($(this).css('font-family') == 'monospace'           // mozilla
                ||  $(this).css('font-family') == '-webkit-monospace'   // Safari
                ||  $(this).css('font-family') == '"Courier New"') {    // Opera
                    var lineHeight   = $(this).css('line-height').replace(/[^0-9]/g, '');
                        lineHeight   = parseFloat(lineHeight);
                    var charsPerLine = this.cols;
                    var charWidth    = parseFloat($(this).innerWidth() / charsPerLine);


                    var iChar = 0;
                    var iLines = 1;
                    var sWord = '';

                    var oSelection = $(this).getSelection();
                    var aLetters = sContent.split("");
                    var aLines = [];

                    for (var w in aLetters) {
                        if (aLetters[w] == "\n") {
                            iChar = 0;
                            aLines.Push(w);
                            sWord = '';
                        } else if (aLetters[w] == " ") {    
                            var wordLength = parseInt(sWord.length);


                            if ((bGTE && iChar + wordLength >= charsPerLine)
                            || (!bGTE && iChar + wordLength > charsPerLine)) {
                                iChar = wordLength + 1;
                                aLines.Push(w - wordLength);
                            } else {                
                                iChar += wordLength + 1; // 1 more char for the space
                            }

                            sWord = '';
                        } else if (aLetters[w] == "\t") {
                            iChar += 4;
                        } else {
                            sWord += aLetters[w];     
                        }
                    }

                    var iLine = 1;
                    for(var i in aLines) {
                        if (oSelection.end < aLines[i]) {
                            iLine = parseInt(i) - 1;
                            break;
                        }
                    }

                    if (iLine > -1) {
                        var x = parseInt(oSelection.end - aLines[iLine]) * charWidth;
                    } else {
                        var x = parseInt(oSelection.end) * charWidth;
                    }
                    var y = (iLine + 1) * lineHeight - this.scrollTop; // below line

                    showTip(oPosition.left + x, oPosition.top + y);
                }
            }

        </script>
    </head>
    <body>
        <form id="tipper">
            <textarea id="textariffic" cols="50">
Aliquam urna. Nullam augue dolor, tincidunt condimentum, malesuada quis, ultrices at, arcu. Aliquam nunc pede, convallis auctor, sodales eget, aliquam eget, ligula. Proin nisi lacus, scelerisque nec, aliquam vel, dictum mattis, eros. Curabitur et neque. Fusce sollicitudin. Quisque at risus. Suspendisse potenti. Mauris nisi. Sed sed enim nec dui viverra congue. Phasellus velit sapien, porttitor vitae, blandit volutpat, interdum vel, enim. Cras sagittis bibendum neque. Proin eu est. Fusce arcu. Aliquam elit nisi, malesuada eget, dignissim sed, ultricies vel, purus. Maecenas accumsan diam id nisi.

Phasellus et nunc. Vivamus sem felis, dignissim non, lacinia id, accumsan quis, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed scelerisque nulla sit amet mi. Nulla consequat, elit vitae tempus vulputate, sem libero rhoncus leo, vulputate viverra nulla purus nec turpis. Nam turpis sem, tincidunt non, congue lobortis, fermentum a, ipsum. Nulla facilisi. Aenean facilisis. Maecenas a quam eu nibh lacinia ultricies. Morbi malesuada orci quis tellus.

Sed eu leo. Donec in turpis. Donec non neque nec ante tincidunt posuere. Pellentesque blandit. Ut vehicula vestibulum risus. Maecenas commodo placerat est. Integer massa nunc, luctus at, accumsan non, pulvinar sed, odio. Pellentesque eget libero iaculis dui iaculis vehicula. Curabitur quis nulla vel felis ullamcorper varius. Sed suscipit pulvinar lectus. 
            </textarea>

        </form>

        <p id="tip">Here I Am!!</p>
    </body>
</html>
35
enobrev

この問題に関連するトピックをロシアのJavaScriptサイトに投稿しました。

ロシア語が理解できない場合は、Googleバージョンで翻訳してみてください: http://translate.google.ru/translate?js=y&prev=_t&hl=ru&ie=UTF-8&layout=1&eotf=1&u=http://javascript .ru/forum/events/7771-poluchit-koordinaty-kursora-v-tekstovom-pole-v-pikselyakh.html&sl = ru&tl = en

翻訳されたバージョンのコード例にはいくつかのマークアップの問題があるため、 元のロシアの投稿でコードを読む できます。

アイデアは簡単です。カーソル位置をピクセル単位で取得するための簡単で普遍的でクロスブラウザな方法はありません。率直に言って、これはInternet Explorer専用です。

他のブラウザでは、本当にそれを計算する必要がある場合は、...

  • 非表示のDIVを作成する
  • テキストボックスのすべてのスタイルとコンテンツをそのDIVにコピーする
  • 次に、キャレットがテキストボックス内にあるテキストの正確に同じ位置にHTML要素を挿入します
  • そのHTML要素の座標を取得する
4
prike

他の投稿でよく説明されているので、これに関連する問題については再度説明しません。可能な解決策を指摘するだけで、いくつかのバグがありますが、それは出発点です

幸い、Githubには、コンテナーに対するキャレットの位置を計算するスクリプトがありますが、jQueryが必要です。 GitHubページはこちら: jquery-caret-position-getter、Bevis.Zhaoに感謝します。

これに基づいて、私は次のコードを実装しました:動作を確認 ここではjsFiddle.netに

<html><head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>- jsFiddle demo by mjerez</title>
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.2.js"></script>
    <link rel="stylesheet" type="text/css" href="http://jsfiddle.net/css/normalize.css">
    <link rel="stylesheet" type="text/css" href="http://jsfiddle.net/css/result-light.css">   
    <script type="text/javascript" src="https://raw.github.com/beviz/jquery-caret-position-getter/master/jquery.caretposition.js"></script>     
    <style type="text/css">
        body{position:relative;font:normal 100% Verdana, Geneva, sans-serif;padding:10px;}
        .aux{background:#ccc;opacity: 0.5;width:50%;padding:5px;border:solid 1px #aaa;}
        .hidden{display:none}
        .show{display:block; position:absolute; top:0px; left:0px;}
    </style>
    <script type="text/javascript">//<![CDATA[ 
    $(document).keypress(function(e) {
        if ($(e.target).is('input, textarea')) {
            var key = String.fromCharCode(e.which);
            var ctrl = e.ctrlKey;
            if (ctrl) {
                var display = $("#autocomplete");
                var editArea = $('#editArea');            
                var pos = editArea.getCaretPosition();
                var offset = editArea.offset();
                // now you can use left, top(they are relative position)
                display.css({
                    left: offset.left + pos.left,
                    top:  offset.top + pos.top,
                    color : "#449"
                })
                display.toggleClass("show");
                return false;
            }
        }

    });
    window.onload = (function() {
        $("#editArea").blur(function() {
            if ($("#autocomplete").hasClass("show")) $("#autocomplete").toggleClass("show");
        })
    });
    //]]>  
    </script>
</head>
<body>
    <p>Click ctrl+space to while you write to diplay the autocmplete pannel.</p>
    </br>
    <textarea id="editArea" rows="4" cols="50"></textarea>
    </br>
    </br>
    </br>
    <div id="autocomplete" class="aux hidden ">
        <ol>
            <li>Option a</li>
            <li>Option b</li>
            <li>Option c</li>
            <li>Option d</li>
        </ol>
    </div>
</body>
4
Ma Jerez

この質問は1か月前に尋ねた質問の複製であり、私はそれに答えましたここ。この質問は何年も前に重複して閉じられていたはずなので、私はそのリンクでのみ回答を維持します。

回答のコピー

meteor-autocomplete のtextareaキャレット座標プラグインを探したので、GitHubで8つのプラグインをすべて評価しました。勝者は、はるかに textarea-caret-position fromComponentです。

特徴

  • ピクセル精度
  • 依存関係は一切ありません
  • ブラウザの互換性:Chrome、Safari、Firefox(にもかかわらず twobugs あります)、IE9 +;動作する可能性がありますが、Opera、IE8以前ではテストされていません
  • 任意のフォントファミリーとサイズ、およびテキスト変換をサポート
  • テキスト領域には任意のパディングまたはボーダーを設定できます
  • テキストエリアの水平または垂直スクロールバーと混同しないでください
  • ハードリターン、タブ(IEを除く)、およびテキスト内の連続するスペースをサポート
  • テキスト領域の列より長い行の正しい位置
  • no 空白の「ゴースト」位置 長い単語を折り返す場合の行末

ここにデモがあります- http://jsfiddle.net/dandv/aFPA7/

enter image description here

使い方

ミラー<div>が画面外に作成され、<textarea>とまったく同じスタイルになります。次に、キャレットまでのテキストエリアのテキストがdivにコピーされ、その直後に<span>が挿入されます。次に、スパンのテキストコンテンツは、偽のdivでのラッピングを忠実に再現するために、textarea内の残りのテキストに設定されます。

これは、長い行の折り返しに関するすべてのEdgeケースを処理することが保証されている唯一の方法です。 @ユーザードロップダウンの位置を決定するためにGitHubでも使用されます。

2
Dan Dascalescu

これは blog が質問に答えるのにも近すぎるようです。私自身は試していませんが、作者はFF3、Chrome、IE、Opera、Safariでテスト済みだと言っています。コードはある GitHub

1
snoopy-do

ここで修正: http://jsfiddle.net/eMwKd/4/

唯一の欠点は、すでに提供されている関数getCaret()が、キーを押したときに間違った位置に解決されることです。そのため、キーを離さない限り、赤いカーソルは実際のカーソルの後ろにあるように見えます。

もう一度見てみましょう。

更新:ふー、行が長すぎると、ワードラップは正確ではありません。

1
lrsjng

多分これはあなたを喜ばせるでしょう、それは選択の位置とカーソルの位置を教えてくれるので、タイマーをチェックして自動位置を取得するか、選択を解除するボタンをクリックして位置を取得しないようにしてください

   <form>
 <p>
 <input type="button" onclick="evalOnce();" value="Get Selection">
timer:
<input id="eval_switch" type="checkbox" onclick="evalSwitchClicked(this)">
<input id="eval_time" type="text" value="200" size="6">
ms
</p>
<textarea id="code" cols="50" rows="20">01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 01234567890123456789012345678901234567890123456789 Sample text area. Please select above text. </textarea>
<textarea id="out" cols="50" rows="20"></textarea>
</form>
<div id="test"></div>
<script>

function Selection(textareaElement) {
this.element = textareaElement;
}
Selection.prototype.create = function() {
if (document.selection != null && this.element.selectionStart == null) {
return this._ieGetSelection();
} else {
return this._mozillaGetSelection();
}
}
Selection.prototype._mozillaGetSelection = function() {
return {
start: this.element.selectionStart,
end: this.element.selectionEnd
 };
 }
Selection.prototype._ieGetSelection = function() {
this.element.focus();
var range = document.selection.createRange();
var bookmark = range.getBookmark();
var contents = this.element.value;
var originalContents = contents;
var marker = this._createSelectionMarker();
while(contents.indexOf(marker) != -1) {
marker = this._createSelectionMarker();
 }
var parent = range.parentElement();
if (parent == null || parent.type != "textarea") {
return { start: 0, end: 0 };
}
range.text = marker + range.text + marker;
contents = this.element.value;
var result = {};
result.start = contents.indexOf(marker);
contents = contents.replace(marker, "");
result.end = contents.indexOf(marker);
this.element.value = originalContents;
range.moveToBookmark(bookmark);
range.select();
return result;
}
Selection.prototype._createSelectionMarker = function() {
return "##SELECTION_MARKER_" + Math.random() + "##";
}

var timer;
var buffer = "";
function evalSwitchClicked(e) {
if (e.checked) {
evalStart();
} else {
evalStop();
}
}
function evalStart() {
var o = document.getElementById("eval_time");
timer = setTimeout(timerHandler, o.value);
}
function evalStop() {
clearTimeout(timer);
}
function timerHandler() {
clearTimeout(timer);
var sw = document.getElementById("eval_switch");
if (sw.checked) {
evalOnce();
evalStart();
}
}
function evalOnce() {
try {
var selection = new Selection(document.getElementById("code"));
var s = selection.create();
var result = s.start + ":" + s.end;
buffer += result;
flush();
 } catch (ex) {
buffer = ex;
flush();
}
}
function getCode() {
// var s.create()
// return document.getElementById("code").value;
}
function clear() {
var out = document.getElementById("out");
out.value = "";
}
function print(str) {
buffer += str + "\n";
}
function flush() {
var out = document.getElementById("out");
out.value = buffer;
buffer = "";
 } 
</script>

ここでデモを見てください: jsbin.com

0
echo_Me

textareaの解決策はわかりませんが、divcontenteditable

Range AP​​Iを使用できます。そうです:(はい、本当に必要なのはこの3行のコードだけです)

// get active selection
var selection = window.getSelection();
// get the range (you might want to check selection.rangeCount
// to see if it's popuplated)
var range = selection.getRangeAt(0);

// will give you top, left, width, height
console.log(range.getBoundingClientRect());

ブラウザの互換性についてはわかりませんが、最新のChrome、Firefox、IE7でも機能することがわかりました(テストしたと思います7、それ以外は9)。

次のような「クレイジー」なことを行うこともできます。"#hash"と入力していて、カーソルが最後のhにある場合は、現在の範囲で#文字を探すことができます、範囲をn文字だけ戻し、その範囲のbounding-rectを取得します。これにより、popup-divがWordに「くっつく」ように見えます。

マイナーな欠点の1つは、contenteditableがときどきバグが多いことです。カーソルは不可能な場所に移動したいので、HTML入力を処理する必要があります。しかし、ブラウザーベンダーはこれらの問題に対処し、より多くのサイトがそれらを使用し始めると確信しています。

私が与えることができるもう1つのヒントは、 rangy ライブラリを見てください。これは、完全な機能を備えた相互互換性のある範囲ライブラリになることを試みます。必要はありませんが、古いブラウザを扱っている場合は、価値があるかもしれません。

0
Halcyon

これ ブログ投稿はあなたの質問に対処しているようですが、残念ながら著者はIE 6。

IE=のDOMは、文字による相対的な位置に関する情報を提供しませんが、ブラウザがレンダリングしたコントロールの境界とオフセットの値を提供します。したがって、これらの値を使用して相対的な次に、JavaScriptのTextRangeを使用して、特定のTextArea内の固定幅フォントの行と列の位置を計算するために、このようなメジャーを使用するメカニズムを作成しました。

まず、TextAreaの相対境界は、使用される固定幅フォントのサイズに基づいて計算する必要があります。これを行うには、TextAreaの元の値をローカルJavaScript変数に格納し、値をクリアする必要があります。次に、TextRangeが作成され、TextAreaの上境界と左境界が決定されます。

0
Adam Bellaire

キャレットオフセットの1つのハックの説明があります: Textarea X/Yキャレット座標-jQueryプラグイン

また、html5機能を使用できる場合は、contenteditable属性を使用してdiv要素を使用することをお勧めします。

0
Andrey Sbrodov