轉載自道招網Javascript保留格式翻譯選區內容及預覽(完結)
前面的一篇文章Javascript保留格式翻譯選區內容及預覽(一),我們已經知道了,怎麼獲取選中的文字,然後交個介面返回,現在我們接著繼續將這麼講翻譯好的詞語替換到原文的對應位置。 首先我們在選擇文字並進行翻譯時,我們的選區還在的,如果後續操作有存在丟掉選區的操作的話,我們需要先將選區暫存起來。
怎麼儲存選區
首先儲存選區的前提是:選區還存在。
怎麼說 ?
如果我們選中的詞語是abc
,如果abc都被替換了,之前儲存的選區資訊也無法使用了,或者不準確了,就不要使用了。
const ranges = [];
for (let i = 0; i < window.getSelection().rangeCount; i++) {
const range = window.getSelection().getRangeAt(i);
ranges.push(range.cloneRange()); // clone後確保ranges可以用來重試
}
我們現在就把選區資訊儲存在`ranges`陣列裡面了
複製程式碼
怎麼恢復選區
既然提到儲存選區,自然要知道怎麼恢復選區了
// 恢復指定選區前移除所有可能存在選區
window.getSelection().removeAllRanges();
ranges.forEach(range => window.getSelection().addRange(range));
複製程式碼
替換原文
替換原文的前提是保持原文處於選中狀態,也就是保留選區,現在這一步上面已經做到了,再加上第一篇提到的獲取的選區的節點的最近一層的共同父節點commonAncestorContainer
。現在我們正式開始進行替換了。
我們用兩個方法完成替換,一個方法(traversalParent)是查詢需要可能需要替換的dom節點。另一個方法(traversalReplace)進行替換。
尋找可能需要替換的dom節點
commonAncestorContainer
裡面是包含我們需要替換的dom節點的,但是裡面可能還有很多無關節點,所以我們必須進行排除。
這就需要window.getSelection().containsNode
方法,它能幫助我們判斷某個節點是不是在選區中。
從MDN上看看它的api
<div>你們好</div>
,但是使用者只選中了“好”字,這時我們就不應該把“你們”給替換了。這部分工作就需要在我們的traversalReplace
方法裡面實現了。
window.getSelection().anchorNode
和window.getSelection().focusNode
分別能讓我們知道選區的起始和結尾的節點。因為我們是人工選擇文字節點,所以我們這裡的起始、結尾節點極大概率是文字節點或者是包含文字的元素節點(只是我們看不見而已,這時就屬於全部包含了,反而簡單)。
文字節點的data
屬性會返回該文字的文字內容。
與anchorNode
和focusNode
對應的window.getSelection().anchorOffset
和window.getSelection().focusOffset
可以告知我們選擇的文字在該文字節點的位置。
我們替換的的原文時就可以用
原文前內容+ 翻譯結果 + 原文結尾內容來替換了。
這兩個方法的程式碼如下
traversalParent(parent, isPreviewMode = false) {
let hit = 0;
let isReached = false;
const childNodes = parent.childNodes;
if (childNodes && childNodes.length > 0) {
for (let j = 0; j < parent.childNodes.length; j++) {
const node = parent.childNodes[j];
if (window.getSelection().containsNode(node)) {
isReached = true;
hit++;
traversalReplace(node, false, win)
} else if (window.getSelection().containsNode(node, true)) {
isReached = true;
hit++;
traversalReplace(node, true, win)
} else {
if (isReached) {
break;
}
}
}
} else if (parent.nodeType === 3) {
traversalReplace(parent, false, window);
}
}
traversalReplace(node, allowPartialContainment, win) {
const childNodes = node.childNodes;
if (childNodes && childNodes.length > 0) {
let isReached = false;
for (let i = 0; i < childNodes.length; i++) {
const item = childNodes[i];
// node部分包含時,再遍歷它的子節點時,要繼續判斷子節點是否包含
if (allowPartialContainment && !window.getSelection().containsNode(item, true)) {
if (isReached) {
break;
}
} else {
isReached = true;
traversalReplace(item, allowPartialContainment, win);
}
}
} else if (node.nodeType === 3) {
const data = node.data || '';
if (data.trim().length < 1) {
return;
}
let targetText = '';
const translatedText = this.translatedWords[this.traversalIndex++] || '';
if (node === window.getSelection().anchorNode) {
// 存在node既是anchorNode又是focusNode的情況
const tailText = node === window.getSelection().focusNode ? node.data.slice(window.getSelection().focusOffset) : '';
targetText = node.data.slice(0, window.getSelection().anchorOffset) + translatedText + tailText;
} else if (node === window.getSelection().focusNode) {
targetText = translatedText + node.data.slice(window.getSelection().focusOffset);
} else {
targetText = translatedText;
}
// 此操作後會改變原來選中該node的Range的startOffset,endOffset等值。皮之不存毛將焉附
node.data = targetText;
}
}
複製程式碼
具體請通過npm安裝translate_selection