textarea文本替换后Ctrl+Z仅能撤销最近操作的技术问询
解决Textarea多替换后Ctrl+Z仅能撤销最后一次的问题
我明白你的困扰——在textarea里多次把选中单词替换成星号后,按Ctrl+Z只能恢复最后一次替换的单词,之前的都找不回来对吧?咱们来看看问题出在哪,再把它搞定。
问题根源
你当前的代码里只用了一个selection对象来存储替换信息,每次新的替换都会覆盖掉上一次的记录。这就导致撤销操作只能找到最后一次的替换数据,自然没法恢复更早的内容。
解决方案:引入撤销历史栈
我们需要用一个数组来维护所有的替换操作历史,每次执行替换时,把当前的替换关键信息(比如替换的起始/结束位置、原文本)存入数组;撤销时,从数组末尾取出最近的一条记录,用它来恢复文本。
下面是修改后的完整代码,我会标注关键改动:
// 把单个selection对象改成数组,存储所有撤销历史 let undoHistory = []; function undo(e) { const evtobj = window.event ? window.event : e; if (evtobj.keyCode === 90 && evtobj.ctrlKey && undoHistory.length > 0) { evtobj.preventDefault(); const txtarea = document.getElementById("mytextarea"); // 取出最后一条历史记录 const lastAction = undoHistory.pop(); const allText = txtarea.value; // 用历史记录恢复文本 const newText = allText.substring(0, lastAction.start) + lastAction.text + allText.substring(lastAction.finish); txtarea.value = newText; // 恢复光标位置(优化体验,贴近原生撤销) txtarea.selectionStart = lastAction.start; txtarea.selectionEnd = lastAction.finish; } } function getSel() { const txtarea = document.getElementById("mytextarea"); const start = txtarea.selectionStart; const finish = txtarea.selectionEnd; const allText = txtarea.value; const selectedText = allText.substring(start, finish); // 把本次替换的信息存入撤销历史 undoHistory.push({ start: start, finish: finish, text: selectedText }); const replacedText = selectedText.replace(/[\S]/g, "*"); const newText = allText.substring(0, start) + replacedText + allText.substring(finish); txtarea.value = newText; $('#newpost').offset({top: 0, left: 0}).hide(); } function closePopUp() { $('#newpost').offset({top: 0, left: 0}).hide(); } $(document).ready(function () { closePopUp(); const newpost = $('#newpost'); $('#mytextarea') .on('select', function (e) { const txtarea = document.getElementById("mytextarea"); const start = txtarea.selectionStart; const finish = txtarea.selectionEnd; newpost.offset(getCursorXY(txtarea, start, 20)).show(); newpost.find('div').text('replace with stars'); }) .on('input', () => { // 当用户手动输入时,清空撤销历史(和原生Ctrl+Z行为一致) undoHistory = []; }); document.onkeydown = undo; }); var getCursorXY = function getCursorXY(input, selectionPoint, offset) { var inputX = input.offsetLeft, inputY = input.offsetTop; var div = document.createElement('div'); var copyStyle = getComputedStyle(input); try { for (var prop of copyStyle) { div.style[prop] = copyStyle[prop]; } } catch (err) { // 捕获迭代器异常,不影响核心功能 } var swap = '.'; var inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value; var textContent = inputValue.substr(0, selectionPoint); div.textContent = textContent; if (input.tagName === 'TEXTAREA') div.style.height = 'auto'; if (input.tagName === 'INPUT') div.style.width = 'auto'; var span = document.createElement('span'); span.textContent = inputValue.substr(selectionPoint) || '.'; div.appendChild(span); document.body.appendChild(div); var spanX = span.offsetLeft, spanY = span.offsetTop; document.body.removeChild(div); return { left: inputX + spanX, top: inputY + spanY + offset }; };
关键改动说明
- 撤销历史数组:用
undoHistory数组替代原来的单个selection对象,每次替换时把start(起始位置)、finish(结束位置)、text(原选中文本)存入数组,保留所有操作记录。 - 撤销逻辑更新:撤销时从数组末尾取出最近的操作记录恢复文本,同时添加了光标位置恢复的优化,让操作体验更贴近浏览器原生撤销行为。
- 手动输入清空历史:当用户在textarea里手动输入内容时,自动清空撤销历史——这和原生Ctrl+Z的逻辑一致,手动输入后之前的替换操作就不再可撤销。
这样修改后,你每次替换单词后,按Ctrl+Z就能依次恢复之前所有被替换的单词啦。
内容的提问来源于stack exchange,提问作者anub
相关产品推荐
相关产品推荐

