簡體   English   中英

JavaScript 將鼠標位置轉換為選擇范圍

[英]JavaScript convert mouse position to selection range

我希望能夠將當前鼠標位置轉換為一個范圍,特別是在 CKEditor 中。

CKEditor 提供了根據范圍設置游標的 API:

var ranges = new CKEDITOR.dom.range( editor.document );
editor.getSelection().selectRanges( [ ranges ] );

由於 CKEditor 提供了此 API,因此可以通過刪除此要求來簡化問題,只需找到一種方法來從包含各種 HTML 元素的 div 上的鼠標坐標生成范圍。

但是,這與將鼠標坐標轉換為文本區域中的光標位置不同,因為文本區域具有固定的列寬和行高,CKEditor 通過 iframe 呈現 HTML。

基於,看起來范圍可以應用於元素。

您如何確定最接近當前鼠標位置的開始/結束范圍?

編輯:關於如何使用 ckeditor API 選擇 mouseup 事件范圍的示例。

editor.document.on('mouseup', function(e) {
    this.focus();
    var node = e.data.$.target;

    var range = new CKEDITOR.dom.range( this.document );
    range.setStart(new CKEDITOR.dom.node(node), 0);
    range.collapse();

    var ranges = [];
    ranges.push(range);
    this.getSelection().selectRanges( ranges );
});

上面例子的問題是事件目標節點 (e.data.$.target) 只為 HTML、BODY 或 IMG 等節點觸發,而不為文本節點觸發。 即使是這樣,這些節點也代表不支持將光標設置為該文本塊內鼠標位置的文本塊。

你在瀏覽器中嘗試做的事情真的很難。 我特別不熟悉 ckeditor,但是常規的 javascript 允許您使用范圍選擇文本,因此我認為它不會添加任何特殊內容。 您必須找到包含點擊的瀏覽器元素,然后在被點擊的元素中找到字符。

檢測瀏覽器元素很簡單:您需要在每個元素上注冊處理程序,或者使用事件的目標字段。 有很多關於這方面的信息,如果這就是你遇到的問題,請在 stackoverflow 上提出更具體的問題。

一旦你有了元素,你需要找出元素中的哪個字符被點擊,然后創建一個適當的范圍將光標放在那里。 正如您鏈接到的帖子所述,瀏覽器的變化使這非常困難。 此頁面有點過時,但對范圍進行了很好的討論: http : //www.quirksmode.org/dom/range_intro.html

范圍無法告訴您它們在頁面上的位置,因此您必須使用另一種技術來找出單擊的文本位。

我從未在 javascript 中看到過完整的解決方案。 幾年前我研究過一個,但我沒有想出一個我滿意的答案(一些非常困難的邊緣情況)。 我使用的方法是一個可怕的黑客:在文本中插入跨度,然后使用它們執行二進制搜索,直到找到包含鼠標單擊的最小跨度。 跨度不會更改布局,因此您可以使用跨度的 position_x/y 屬性來找出它們包含點擊。

例如,假設您在節點中有以下文本:

<p>Here is some paragraph text.</p>

我們知道點擊出現在本段的某個地方。 用 span 將段落分成兩半:

<p><span>Here is some p</span>aragraph text.</p>

如果跨度包含點擊坐標,則繼續在那半部分進行二分搜索,否則搜索后半部分。

這適用於單行,但如果文本跨越多行,您必須首先找到換行符,否則跨度可能會重疊。 您還必須弄清楚當點擊不是在任何文本上而是在元素中時該怎么做——例如,超過段落中最后一行的末尾。

自從我在這個瀏覽器上工作以來,速度已經快了很多。 他們現在可能足夠快,可以在每個字符周圍添加 s,然后在每兩個字符周圍添加 s 以創建易於搜索的二叉樹。 您可以嘗試這種方法 - 它可以更輕松地確定您正在處理的線路。

TL;DR 這是一個非常困難的問題,如果有答案,可能不值得您花時間提出它。

很抱歉碰到一個舊線程,但我想在這里發布這個,以防其他人偶然發現這個問題,因為這方面的信息很少。 我只需要編寫一個函數來為 Outlook for web 用戶腳本執行此操作,因為它們會覆蓋默認的拖放功能並在撰寫框中中斷它。 這是我想出的解決方案:

function rangeFromCoord(x, y) {
    const closest = {
        offset: 0,
        xDistance: Infinity,
        yDistance: Infinity,
    };

    const {
        minOffset,
        maxOffset,
        element,
    } = (() => {
        const range = document.createRange();
        range.selectNodeContents(document.elementFromPoint(x, y));
        return {
            element: range.startContainer,
            minOffset: range.startOffset,
            maxOffset: range.endOffset,
        };
    })();

    for(let i = minOffset; i <= maxOffset; i++) {
        const range = document.createRange();
        range.setStart(element, i);
        range.setEnd(element, i);
        const marker = document.createElement("span");
        marker.style.width = "0";
        marker.style.height = "0";
        marker.style.position = "absolute";
        marker.style.overflow = "hidden";
        range.insertNode(marker);
        const rect = marker.getBoundingClientRect();
        const distX = Math.abs(x - rect.left);
        const distY = Math.abs(y - rect.top);
        marker.remove();
        if(closest.yDistance > distY) {
            closest.offset = i;
            closest.xDistance = distX;
            closest.yDistance = distY;
        } else if(closest.yDistance === distY) {
            if(closest.xDistance > distX) {
                closest.offset = i;
                closest.xDistance = distX;
                closest.yDistance = distY;
            }
        }
    }

    const range = document.createRange();
    range.setStart(element, closest.offset);
    range.setEnd(element, closest.offset);
    return range;
}

您所做的只是傳入客戶端坐標,該函數將自動選擇該位置最具體的元素。 它將使用該選擇來獲取瀏覽器使用的父元素(最顯着的是contenteditable元素),以及最大和最小偏移量。 然后它將繼續,遍歷偏移量,放置具有position: absolute; width: 0; height: 0; overflow: hidden; marker跨度元素position: absolute; width: 0; height: 0; overflow: hidden; position: absolute; width: 0; height: 0; overflow: hidden; 在每個偏移量處探測它們的位置,移除它們並檢查距離。 對於大多數文本編輯器,它會首先在 Y 坐標上盡可能接近,然后在 X 坐標上移動。 一旦找到最近的位置,它就會創建一個新的選擇並返回它。

有兩種方法可以做到這一點,就像每個所見即所得一樣。

第一: - 你放棄是因為它太難了,它最終會成為瀏覽器殺手;

第二: - 您嘗試解析文本並將其放在原始文本上方的半透明文本區域或 div 中的確切位置,但這里我們有兩個問題:

1)您將如何解析動態數據塊以僅獲取文本並確保將其映射到實際內容的確切位置

2)您將如何解決更新以解析您鍵入的每個該死的字符或您在編輯器中執行的每個操作。

最后,這只是“DOM 樹陰暗面的殘酷奧德賽”,但如果您選擇第二種方式,那么您帖子中的代碼將像魅力一樣工作。

我正在做一個類似的任務,以允許 TinyMCE(內聯模式)使用放置在鼠標單擊位置的插入符號進行初始化。 以下代碼至少適用於最新的 Firefox 和 Chrome:

let contentElem  = $('#editorContentRootElem');
let editorConfig = { inline: true, forced_root_block: false };

let onFirstFocus = () => {
  contentElem.off('click focus', onFirstFocus);

  setTimeout(() => {
    let uniqueId = 'uniqueCaretId';
    let range    = document.getSelection().getRangeAt(0);
    let caret    = document.createElement("span");
    range.surroundContents(caret);
    caret.outerHTML = `<span id="${uniqueId}" contenteditable="false"></span>`;

    editorConfig.setup = (editor) => {
      this.editor = editor;

      editor.on('init', () => {
        var caret = $('#' + uniqueId)[0];
        if (!caret) return;

        editor.selection.select(caret);
        editor.selection.collapse(false);
        caret.parentNode.removeChild(caret);
      });
    };

    tinymce.init(editorConfig);         
  }, 0); // after redraw
}; // onFirstFocus

contentElem.on('click focus', onFirstFocus);

解釋

似乎在鼠標單擊/焦點事件和重繪 (setTimeout ms 0) 之后document.getSelection().getRangeAt(0)返回有效的光標范圍。 我們可以將它用於任何目的。 TinyMCE 在初始化時移動插入符號開始,所以我在當前范圍開始創建特殊的跨度“插入符號”元素,然后強制編輯器選擇它,然后將其刪除。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM