当前位置:首页 > 文章列表 > 文章 > 前端 > Blazor富文本编辑器JSInterop与点击事件处理

Blazor富文本编辑器JSInterop与点击事件处理

2025-11-28 12:39:31 0浏览 收藏

本篇文章给大家分享《Blazor富文本编辑器JSInterop与点击事件问题解决》,覆盖了文章的常见基础知识,其实一个语言的全部知识点一篇文章是不可能说完的,但希望通过这些问题,让读者对自己的掌握程度有一定的认识(B 数),从而弥补自己的不足,更好的掌握它。

解决Blazor富文本编辑器中JSInterop与OnClick事件的常见问题

本文深入探讨了在Blazor应用中利用JSInterop构建富文本编辑器时,因事件处理机制和组件重渲染导致的双击、重复提示及内容丢失问题。通过优化JSInterop调用方式,将命令直接从Blazor传递给JavaScript,并利用Blazor组件的`ShouldRender`生命周期方法来控制`contenteditable`区域的渲染行为,我们能够构建一个高效且稳定的富文本编辑器。

1. 理解Blazor与JSInterop交互的常见陷阱

在Blazor应用中结合JavaScript的document.execCommand来创建富文本编辑器时,开发者常常会遇到一些看似棘手的问题,例如按钮需要双击才能生效、图片插入提示重复弹出,以及插入的图片或文本内容在页面刷新后消失。这些问题通常源于对Blazor组件渲染机制和JSInterop事件处理方式的误解。

陷阱一:重复注册事件监听器

原始的JavaScript代码设计存在一个核心问题:它在每次Blazor的@onclick事件触发时,都会遍历所有按钮并为它们重新注册一个click事件监听器。

// 原始的JSInterop.js
function buttonPressed() {
    const elements = document.querySelectorAll('.btn');
    elements.forEach(element => {
        // 每次调用buttonPressed()都会为每个按钮添加一个新的click监听器
        element.addEventListener('click', () => {
            let command = element.dataset['element'];
            if (command == 'createLink' || command == 'insertImage') {
                let url = prompt('Enter the link here:', 'http://');
                document.execCommand(command, false, url);
            } else {
                document.execCommand(command, false, null);
            }
        });
    });
}

其执行流程如下:

  1. 首次点击Blazor按钮: Blazor的@onclick事件触发,调用showChange方法,进而通过JSInterop调用buttonPressed()。此时,JavaScript代码为每个.btn元素注册了第一个click事件监听器。但由于是Blazor事件触发的JS调用,这些新注册的JS事件处理器还未被触发。
  2. 第二次点击同一个Blazor按钮: 此时,Blazor的@onclick再次触发,showChange再次调用buttonPressed()。JavaScript代码会再次为所有.btn元素注册第二个click事件监听器。同时,由于现在按钮上已经有了事件监听器,第一次注册的监听器也会被触发,导致document.execCommand执行。对于insertImage这类需要prompt的命令,就会出现重复提示的问题。

这种重复注册导致了事件处理的混乱和效率低下。

陷阱二:Blazor组件的默认重渲染行为

Blazor组件在检测到其状态发生变化(例如,通过@onclick事件处理程序)时,会默认进行重渲染。这意味着组件的RenderTree会被重新构建,并且对应的DOM元素可能会被更新或替换。

<!-- 原始的HTML结构 -->
<div class="main-content">
    <!-- ... 按钮 ... -->
    <div class="content" id="content" contenteditable="true"></div>
</div>

对于contenteditable="true"的div元素,当Blazor组件因按钮点击而重渲染时,这个div的内容可能会被清空,因为它被Blazor的渲染引擎重新创建或更新了。因此,即使document.execCommand成功地插入了图片或文本,这些内容也会在Blazor的下一次渲染周期中被擦除,导致用户看不到任何变化。

2. 优化JSInterop调用:直接传递命令

为了解决重复注册事件监听器的问题,我们应该改变JSInterop的调用模式:让Blazor直接将要执行的命令传递给JavaScript,而不是让JavaScript去查找元素并注册事件。

C# 代码调整

我们将showChange方法修改为接受一个string command参数,并将其直接传递给JavaScript函数。

// Blazor组件的C#代码块
@inject IJSRuntime JsRuntime

async Task showChange(string command)
{
    // 直接将命令传递给JavaScript函数
    await JsRuntime.InvokeVoidAsync(identifier: "buttonPressed", command);
}

HTML 按钮绑定调整

现在,每个按钮的@onclick事件将直接调用showChange方法,并传入其对应的命令字符串。

<div class="text-editor-header">
    <button @onclick='@(() => showChange("bold"))' type="button" class="btn" data-element="bold">
        <i class="fa fa-bold"></i>
    </button>
    <button @onclick='@(() => showChange("justifyFull"))' type="button" class="btn" data-element="justifyFull">
        <span class="fa fa-align-justify"></span>
    </button>
    <button @onclick='@(() => showChange("insertImage"))' type="button" class="btn" data-element="insertImage">
        <span class="fa fa-image"></span>
    </button>
    <!-- 其他按钮类似修改 -->
</div>

JavaScript 代码调整

buttonPressed函数现在直接接收Blazor传递的command参数,并立即执行document.execCommand。这样就彻底移除了事件监听器的重复注册问题。

// JSInterop.js
function buttonPressed(command) {
    if (command == 'createLink' || command == 'insertImage') {
        let url = prompt('Enter the link here:', 'http://');
        document.execCommand(command, false, url);
    } else {
        document.execCommand(command, false, null);
    }
}

通过这些修改,每次按钮点击都会直接且唯一地触发JavaScript中的document.execCommand,解决了双击和重复提示的问题。

3. 控制contenteditable区域的渲染行为

即使命令执行正确,Blazor的默认重渲染行为仍然会清除contenteditable区域的内容。为了解决这个问题,我们需要将contenteditable区域封装成一个独立的Blazor组件,并重写其ShouldRender方法来阻止其重渲染。

创建独立的RichTextContent组件

创建一个新的Blazor组件,例如RichTextContent.razor,它只包含contenteditable的div。

<!-- RichTextContent.razor -->
<div class="content" id="content" contenteditable="true"></div>

@code {
    // 阻止此组件在父组件或自身状态改变时重渲染
    protected override bool ShouldRender() => false;
}

重写ShouldRender方法

在RichTextContent.razor组件的@code块中,我们重写ShouldRender()方法并使其返回false。

protected override bool ShouldRender() => false;

ShouldRender()方法的作用:ShouldRender()是Blazor组件生命周期方法的一部分。当它返回false时,Blazor的渲染引擎将跳过此组件及其子组件的渲染过程,即使其父组件或自身状态发生了变化。这确保了contenteditable div的DOM状态(包括用户输入或document.execCommand修改的内容)不会被Blazor的重渲染机制意外清除。

在主组件中使用RichTextContent

现在,在你的主组件中,你可以简单地引用这个新的RichTextContent组件。

<!-- 主组件的HTML -->
<div class="main-content">
    <!--Text Editor Header-->
    <div class="text-editor-header">
        <!-- ... 按钮 (如上面优化后的代码) ... -->
    </div>
    <!-- 使用独立的RichTextContent组件 -->
    <RichTextContent />
</div>

注意事项: 使用ShouldRender() => false意味着RichTextContent组件将不再响应Blazor的数据绑定或状态更新。如果你的富文本编辑器需要从Blazor端动态加载初始内容或进行其他Blazor驱动的更新,你可能需要通过JSInterop手动将内容注入到contenteditable div中,或者实现更复杂的ShouldRender逻辑,例如只在特定条件满足时才允许渲染。但在大多数document.execCommand驱动的富文本编辑器场景中,这种方法是有效且简化的。

4. 完整示例

将上述所有修改整合后,你的Blazor富文本编辑器将具备稳定且高效的事件处理和内容管理能力。

RichTextEditor.razor (主组件)

@page "/richtexteditor"
@inject IJSRuntime JsRuntime

<div class="main-content">
    <!--Text Editor Header-->
    <div class="text-editor-header">
        <button @onclick='@(() => showChange("bold"))' type="button" class="btn">
            <i class="fa fa-bold"></i>
        </button>
        <button @onclick='@(() => showChange("italic"))' type="button" class="btn">
            <i class="fa fa-italic"></i>
        </button>
        <button @onclick='@(() => showChange("underline"))' type="button" class="btn">
            <i class="fa fa-underline"></i>
        </button>
        <button @onclick='@(() => showChange("strikeThrough"))' type="button" class="btn">
            <i class="fa fa-strikethrough"></i>
        </button>
        <button @onclick='@(() => showChange("justifyLeft"))' type="button" class="btn">
            <span class="fa fa-align-left"></span>
        </button>
        <button @onclick='@(() => showChange("justifyCenter"))' type="button" class="btn">
            <span class="fa fa-align-center"></span>
        </button>
        <button @onclick='@(() => showChange("justifyRight"))' type="button" class="btn">
            <span class="fa fa-align-right"></span>
        </button>
        <button @onclick='@(() => showChange("justifyFull"))' type="button" class="btn">
            <span class="fa fa-align-justify"></span>
        </button>
        <button @onclick='@(() => showChange("insertOrderedList"))' type="button" class="btn">
            <span class="fa fa-list-ol"></span>
        </button>
        <button @onclick='@(() => showChange("insertUnorderedList"))' type="button" class="btn">
            <span class="fa fa-list-ul"></span>
        </button>
        <button @onclick='@(() => showChange("createLink"))' type="button" class="btn">
            <span class="fa fa-link"></span>
        </button>
        <button @onclick='@(() => showChange("insertImage"))' type="button" class="btn">
            <span class="fa fa-image"></span>
        </button>
        <button @onclick='@(() => showChange("undo"))' type="button" class="btn">
            <span class="fa fa-undo"></span>
        </button>
        <button @onclick='@(() => showChange("redo"))' type="button" class="btn">
            <span class="fa fa-redo"></span>
        </button>
    </div>

    <!-- 使用独立的RichTextContent组件来承载可编辑内容 -->
    <RichTextContent />
</div>

@code {
    async Task showChange(string command)
    {
        await JsRuntime.InvokeVoidAsync(identifier: "buttonPressed", command);
    }
}

RichTextContent.razor (内容组件)

<!-- RichTextContent.razor -->
<div class="content" id="content" contenteditable="true" style="border: 1px solid #ccc; min-height: 200px; padding: 10px;">
    <!-- 初始内容或用户编辑的内容将在此处保留 -->
    <p>开始在这里输入你的文本...</p>
</div>

@code {
    // 阻止此组件在父组件或自身状态改变时重渲染
    // 这确保了contenteditable div的DOM状态不会被Blazor意外清除
    protected override bool ShouldRender() => false;
}

wwwroot/js/JSInterop.js (JavaScript文件)

function buttonPressed(command) {
    // 确保在执行execCommand前,contenteditable div是焦点
    // 这是一个常见的最佳实践,以确保命令作用于正确的位置
    const contentDiv = document.getElementById('content');
    if (contentDiv) {
        contentDiv.focus();
    }

    if (command === 'createLink' || command === 'insertImage') {
        let url = prompt('请输入链接或图片URL:', 'http://');
        if (url) { // 只有当用户输入了URL时才执行命令
            document.execCommand(command, false, url);
        }
    } else {
        document.execCommand(command, false, null);
    }
}

注意: 确保在_Host.cshtml (Blazor Server) 或 index.html (Blazor WebAssembly) 中正确引用了JSInterop.js文件。

<!-- 例如在_Host.cshtml或index.html中 -->
<script src="js/JSInterop.js"></script>

5. 总结与最佳实践

通过上述优化,我们解决了在Blazor中构建富文本编辑器时遇到的核心问题。以下是一些关键的总结和最佳实践:

  • 理解Blazor渲染机制: Blazor组件的默认重渲染行为可能会意外地清除或重置由JavaScript直接操作的DOM元素内容。
  • 控制组件渲染: 对于需要保留DOM状态的元素(如contenteditable),将其封装为独立组件并重写ShouldRender()方法返回false,是阻止Blazor重渲染的有效策略。
  • 优化JSInterop调用: 避免在JSInterop调用的JavaScript函数中重复注册事件监听器。最佳实践是让Blazor直接将操作命令和必要参数传递给JavaScript函数,由JavaScript函数直接执行命令。
  • 直接执行命令: JavaScript函数应直接执行document.execCommand,而不是通过额外的DOM事件监听器来间接触发。
  • 焦点管理: 在执行document.execCommand之前,确保contenteditable元素获得焦点,以确保命令作用于正确的选区。
  • 用户输入验证: 对于createLink或insertImage等需要用户输入的命令,在执行document.execCommand之前,对用户输入进行简单的验证(例如,检查prompt是否返回null或空字符串),可以提升用户体验。

遵循这些原则,你将能够更有效地在Blazor应用中集成JavaScript功能,构建出稳定且功能强大的富文本编辑器。

到这里,我们也就讲完了《Blazor富文本编辑器JSInterop与点击事件处理》的内容了。个人认为,基础知识的学习和巩固,是为了更好的将其运用到项目中,欢迎关注golang学习网公众号,带你了解更多关于的知识点!

Golang路由动态参数解析详解Golang路由动态参数解析详解
上一篇
Golang路由动态参数解析详解
美图秀秀照片加载慢解决方法
下一篇
美图秀秀照片加载慢解决方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ChatExcel酷表:告别Excel难题,北大团队AI助手助您轻松处理数据
    ChatExcel酷表
    ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
    3161次使用
  • Any绘本:开源免费AI绘本创作工具深度解析
    Any绘本
    探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
    3374次使用
  • 可赞AI:AI驱动办公可视化智能工具,一键高效生成文档图表脑图
    可赞AI
    可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
    3402次使用
  • 星月写作:AI网文创作神器,助力爆款小说速成
    星月写作
    星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
    4505次使用
  • MagicLight.ai:叙事驱动AI动画视频创作平台 | 高效生成专业级故事动画
    MagicLight
    MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
    3783次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码