You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态创建Summernote富文本编辑器后格式操作光标跳转问题

动态创建Summernote编辑器光标跳转问题解决

问题现象

已引入JQuery和Bootstrap,静态HTML中使用Summernote富文本编辑器一切正常,但通过JavaScript动态创建多个编辑器后,初始化及保存功能正常,执行加粗、斜体或修改字体颜色等格式操作时,光标会跳转到行首,无法正常选中文本修改。

相关代码

创建编辑器的函数

function createRTE(lang, field_name = 'description', required = true) {
    const rte_div = document.createElement('div');

    const rte = document.createElement('div');
    rte.setAttribute('name', 'content[' + global_counter + '][' + field_name +']['+lang+']');
    rte.setAttribute('class', 'summernote');
    rte.setAttribute('data-textarea', 'rte_' + global_counter + '_' + lang);

    const rte_textarea = document.createElement('textarea');
    rte_textarea.setAttribute('name', 'content[' + global_counter + '][' + field_name +']['+lang+']');
    rte_textarea.setAttribute('id', 'rte_' + global_counter + '_' + lang);
    rte_textarea.setAttribute('hidden', 'true');

    rte_div.appendChild(rte_textarea);
    rte_div.appendChild(rte);

    return rte_div;
}

初始化编辑器的函数

function initSummernote(selector = '.summernote') {
    if (!$.fn.summernote) {
        return;
    }

    const elems = document.querySelectorAll(selector);

    for (const elem of elems) {
        $(elem).summernote({
            dialogsFade: true,
            dialogsInBody: true,
            height: elem.dataset.height ?? 300,
            fontNames: [],
            maximumImageFileSize: 1024 * 1024,
            tableClassName: 'table table-hover table-bordered',
            toolbar: [
                ['actions', ['undo', 'redo']],
                ['style', ['style']],
                ['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear']],
                ['color', ['color']],
                ['para', ['ul', 'ol', 'paragraph', 'hr']],
                ['table', ['table']],
                ['insert', ['link', 'picture', 'video']],
                ['view', ['fullscreen', 'codeview', 'help']]
            ],
            callbacks: {
                onChange(contents) {
                    if (elem.dataset.textarea) {
                        document.getElementById(elem.dataset.textarea).value = $(elem).summernote('isEmpty') ? '' : contents.trim();
                    }
                }
            }
        });
    }
}

问题原因

核心问题出在onChange回调中实时同步内容到隐藏textarea的操作:每次编辑器内容变化(包括格式修改)都会直接更新textarea的值,这个操作会干扰Summernote内部的光标位置管理,导致格式操作后光标跳转到行首。

另外,初始化函数中遍历所有.summernote元素,若重复调用会对已初始化的编辑器重复执行初始化,也可能引发异常。

解决方案

方案1:修改内容同步时机,避免实时同步

将onChange的实时同步改为表单提交时统一同步,彻底避免光标干扰:

  1. 移除onChange回调,调整初始化函数:
function initSummernote(selector = '.summernote') {
    if (!$.fn.summernote) {
        return;
    }

    // 只初始化未被初始化过的元素,避免重复初始化
    const elems = document.querySelectorAll(`${selector}:not(.note-editor)`);

    for (const elem of elems) {
        $(elem).summernote({
            dialogsFade: true,
            dialogsInBody: true,
            height: elem.dataset.height ?? 300,
            fontNames: [],
            maximumImageFileSize: 1024 * 1024,
            tableClassName: 'table table-hover table-bordered',
            toolbar: [
                ['actions', ['undo', 'redo']],
                ['style', ['style']],
                ['font', ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'clear']],
                ['color', ['color']],
                ['para', ['ul', 'ol', 'paragraph', 'hr']],
                ['table', ['table']],
                ['insert', ['link', 'picture', 'video']],
                ['view', ['fullscreen', 'codeview', 'help']]
            ]
            // 移除onChange回调
        });
    }
}
  1. 添加表单提交事件处理,统一同步内容:
// 替换为你的表单选择器
document.querySelector('form').addEventListener('submit', function(e) {
    $('.summernote').each(function() {
        const textareaId = $(this).data('textarea');
        if (textareaId) {
            const contents = $(this).summernote('isEmpty') ? '' : $(this).summernote('code').trim();
            document.getElementById(textareaId).value = contents;
        }
    });
});

方案2:保留实时同步但优化光标处理

如果必须实时同步,可通过保存/恢复光标位置解决:

callbacks: {
    onChange(contents) {
        if (elem.dataset.textarea) {
            // 保存当前光标位置
            const range = $(elem).summernote('createRange');
            // 延迟执行同步,避免干扰格式操作的光标处理
            setTimeout(() => {
                document.getElementById(elem.dataset.textarea).value = $(elem).summernote('isEmpty') ? '' : contents.trim();
                // 恢复光标位置
                $(elem).summernote('restoreRange', range);
            }, 10);
        }
    }
}

额外优化:避免重复初始化

修改初始化函数的选择器,只针对未初始化的元素(Summernote初始化后会添加.note-editor类),防止重复初始化引发异常:

const elems = document.querySelectorAll(`${selector}:not(.note-editor)`);

问题截图

  • 选中文本并点击格式按钮:
    选中文本并点击格式按钮
  • 点击格式按钮后光标跳转至行首:
    点击格式按钮后光标跳转至行首

内容的提问来源于stack exchange,提问作者Leon Kardaš

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 03:12:11