60

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

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

4

11 に答える 11

35

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

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

まだこれを機能させようとしている人がいることに気付いた後、私は新しいアプローチを試すことにしました。今回の私の結果は、少なくともLinuxのグーグルクロームでははるかに優れています。Windows PCを利用できなくなったため、Ubuntuのchrome/firefoxでしかテストできません。私の結果はChromeで100%一貫して機能し、Firefoxで約70〜80%としましょう。しかし、矛盾を見つけるのが信じられないほど難しいとは思いません。

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

これは間違いなくハックであり、コードをまとめてしまったことを前もってお詫びします。少なくとも、グーグルクロームでは、設定したフォントやテキストエリアのサイズに関係なく、一貫して機能します。Sam Saffronの例を使用して、カーソル座標(灰色の背景のdiv)を表示しました。また、「ランダム化」リンクを追加したので、さまざまなフォント/ texareaのサイズとスタイルで機能し、カーソル位置がその場で更新されるのを見ることができます。コンパニオンキャンバスが一緒に再生されるのをよりよく見ることができるように、全ページのデモを見ることをお勧めします。

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

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

まず第一に、テキストエリアのサイズに一致するようにキャンバスを調整します。キャンバスのサイズは実際には結果に影響を与えないため、これは完全に視覚的な目的です。Canvasは実際にはワードラップの手段を提供していないため、テキストエリアにできるだけ一致するように行を分割する手段を思いつく(盗む/借りる/まとめる)必要がありました。これは、最もクロスブラウザの調整を行う必要があると思われる場所です。

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

を使用していることを除いて、x座標は非常に似ていますcontext.measureText。適切な数の文字を印刷している限り、Canvasに描画される線の幅がわかります。これは、現在のselection.end位置の前の文字である最後の文字が書き出された後に終了します。

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

以下にソースを貼り付けます。コピーして貼り付けることができるはずですが、そうする場合は、サーバー上のコピーをヒットするのではなく、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を直接見つける方法はありません。Adam Bellaire言及したIEメソッドは興味深いものですが、残念ながらクロスブラウザーではありません。次善の策は、文字をグリッドとして使用することだと思いました。

残念ながら、どのブラウザにもフォントメトリック情報は組み込まれていません。つまり、一貫した測定値を持つフォントタイプはモノスペースフォントだけです。また、フォントの高さからフォントの幅を計算する信頼できる方法はありません。最初は、高さのパーセンテージを使用してみましたが、これはうまくいきました。次に、フォントサイズを変更すると、すべてが地獄に落ちました。

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

私が遭遇した次の問題は、cssを介してフォントを設定した場合でも、ブラウザーがフォントを異なる方法で報告することです。フォントを設定しない場合、mozillaはmonospaceデフォルトで使用し、IEはCourier NewOpera "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は最後の単語をラインから外します。

次に、すべての行の最初の文字の位置の配列を作成します。textarea内のすべての文字をループします。改行の場合は、ラインアレイに新しい位置を追加します。スペースの場合、現在の「単語」が現在の行に収まるかどうか、または次の行にプッシュされるかどうかを判断しようとします。句読点は「単語」の一部としてカウントされます。タブでテストしたことはありませんが、タブ文字に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>
于 2008-10-02T16:47:34.453 に答える
4

この問題に関連するトピックをロシアの 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 要素の座標を取得する
于 2010-02-18T17:26:30.560 に答える
4

この質問は、1 か月前に尋ねられた質問と重複していることに注意してください。ここで回答しました。この質問は何年も前に重複して閉じられているはずなので、そのリンクでのみ回答を維持します。

答えのコピペ

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

特徴

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

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

ここに画像の説明を入力

使い方

ミラー<div>はオフスクリーンで作成され、 とまったく同じようにスタイル設定されます<textarea>。次に、キャレットまでの textarea のテキストが div にコピーされ、その<span>直後に a が挿入されます。次に、偽の div での折り返しを忠実に再現するために、スパンのテキスト コンテンツが textarea の残りのテキストに設定されます。

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

于 2014-03-17T12:50:26.357 に答える
4

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

幸いなことに、コンテナに対するキャレットの位置を計算するスクリプトが 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>
于 2012-10-26T08:23:48.027 に答える
1

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

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

私はそれをもう一度見てみましょう。

更新: うーん、行が長すぎる場合、ワードラップは正確ではありません..

于 2012-10-27T20:51:43.230 に答える
1

このブログは、質問に答えすぎているようです。私は自分で試したことはありませんが、著者は、FF3、Chrome、IE、Opera、Safari でテスト済みだと言っています。コードはGitHubにあります

于 2012-10-24T21:48:31.967 に答える
0

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

   <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

于 2012-10-28T18:13:54.287 に答える
0

の解決策はわかりませんが、 withtextareaに対しては確かに機能します。divcontenteditable

RangeAPIを使用できます。そのように: (はい、本当に必要なのはこの 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を取得できます。 、これにより popup-div が単語に「固執」しているように見えます。

マイナーな欠点の 1 つはcontenteditable、少しバグがある場合があることです。カーソルは不可能な場所に移動するのが好きで、HTML 入力を処理する必要があります。しかし、ブラウザー ベンダーがこれらの問題に対処し、それらを使用するサイトが増えることは間違いありません。

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

于 2012-10-26T00:06:13.177 に答える
0

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

contenteditableまた、 html5 の機能を利用できるのであれば、属性を持つ div 要素を使用することをお勧めします。

于 2012-10-30T19:14:05.753 に答える
0

このブログ投稿はあなたの質問に答えているようですが、残念ながら著者は IE 6 でしかテストしていないことを認めています。

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

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

于 2008-09-24T17:18:41.960 に答える
-1

クローン div にスパン要素を追加し、このスパンのオフセットに基づいて偽のカーソルを設定するのはどうですか? ここであなたのフィドルを更新しました。また、ここにJSビットのみがあります

// http://stackoverflow.com/questions/263743/how-to-get-caret-position-in-textarea
var map = [];
var pan = '<span>|</span>'

//found @ http://davidwalsh.name/detect-scrollbar-width

function getScrollbarWidth() {
    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 getCaret(el) {
    if (el.selectionStart) {
        return el.selectionStart;
    } else if (document.selection) {
        el.focus();

        var r = document.selection.createRange();
        if (r == null) {
            return 0;
        }

        var re = el.createTextRange(),
            rc = re.duplicate();
        re.moveToBookmark(r.getBookmark());
        rc.setEndPoint('EndToStart', re);

        return rc.text.length;
    }
    return 0;
}


$(function() {
    var span = $('#pos span');
    var textarea = $('textarea');

    var note = $('#note');

    css = getComputedStyle(document.getElementById('textarea'));
    try {
        for (i in css) note.css(css[i]) && (css[i] != 'width' && css[i] != 'height') && note.css(css[i], css.getPropertyValue(css[i]));
    } catch (e) {}

    note.css('max-width', '300px');
    document.getElementById('note').style.visibility = 'hidden';
    var height = note.height();
    var fakeCursor, hidePrompt;

    textarea.on('keyup click', function(e) {
        if (document.getElementById('textarea').scrollHeight > 100) {
            note.css('max-width', 300 - getScrollbarWidth());
        }

        var pos = getCaret(textarea[0]);

        note.text(textarea.val().substring(0, pos));
        $(pan).appendTo(note);
        span.text(pos);

        if (hidePrompt) {
            hidePrompt.remove();
        }
        if (fakeCursor) {
            fakeCursor.remove();
        }

        fakeCursor = $("<div style='width:5px;height:30px;background-color: #777;position: absolute;z-index:10000'>&nbsp;</div>");

        fakeCursor.css('opacity', 0.5);
        fakeCursor.css('left', $('#note span').offset().left + 'px');
        fakeCursor.css('top', textarea.offset().top + note.height() - (30 + textarea.scrollTop()) + 'px');

        hidePrompt = fakeCursor.clone();
        hidePrompt.css({
            'width': '2px',
            'background-color': 'white',
            'z-index': '1000',
            'opacity': '1'
        });

        hidePrompt.appendTo(textarea.parent());
        fakeCursor.appendTo(textarea.parent());



        return true;
    });
});

更新 : 最初の行にハード改行が含まれていない場合はエラーが発生することがわかりますが、含まれている場合はうまく機能しているようです。

于 2012-10-31T05:11:08.630 に答える