Chat
Ask me anything
Ithy Logo

使用 Dioxus 0.6 实现桌面应用中的搜索高亮效果

How Nextcloud is the ultimate open source productivity suite ...

在使用 textarea 组件开发桌面应用时,常常会遇到无法通过 CSS 对输入内容进行部分高亮显示的问题。由于 textarea 本质上是一个纯文本输入框,它不支持在文本中嵌入 HTML 或应用局部样式,因此直接在 textarea 中实现类似网页搜索的高亮效果是不可能的。不过,我们可以通过一些替代方法来达到类似的效果。本文将详细介绍几种可行的解决方案,帮助你在 Dioxus 0.6 框架下实现搜索高亮功能。

解决方案一:使用 contenteditablediv 替代 textarea

由于 textarea 无法直接渲染部分样式,我们可以使用一个可编辑的 div 元素来替代它。通过设置 contenteditable="true"div 可以像 textarea 一样接受用户输入,同时支持 HTML 和 CSS 渲染,从而实现部分文本的高亮显示。

原理

利用 div 的可编辑特性,我们可以在用户输入的文本中动态插入 <span> 标签,并应用 CSS 样式来高亮匹配的搜索词。这种方法允许我们对特定的文本片段进行样式化,从而实现搜索高亮效果。

实现步骤

  1. 创建一个可编辑的 div

    使用 contenteditable="true" 属性,使 div 可以接受用户输入。

  2. 监听用户输入事件:

    通过 oninput 事件捕捉用户输入,并根据搜索关键词动态更新内容。

  3. 动态插入高亮标签:

    在用户输入后,使用 Rust 代码将匹配的搜索词包裹在 <span> 标签中,并应用高亮样式。

  4. 处理光标位置:

    更新内容时,需要确保光标位置的稳定,避免用户体验受影响。

示例代码

以下是一个使用 contenteditablediv 实现搜索高亮的完整示例:


use dioxus::prelude::*;

fn main() {
    dioxus::desktop::launch(App);
}

fn App(cx: Scope) -> Element {
    let text = use_state(cx, || String::new());
    let search_query = use_state(cx, || String::new());

    let highlighted_html = highlight_text(&text, &search_query);

    cx.render(rsx! {
        div {
            style: "font-family: monospace; padding: 20px;",
            input {
                type: "text",
                placeholder: "输入搜索关键词",
                value: "{search_query}",
                oninput: move |e| search_query.set(e.value.clone()),
                style: "margin-bottom: 10px; width: 100%; padding: 8px;"
            }
            div {
                contenteditable: "true",
                oninput: move |e| text.set(e.value.clone()),
                dangerously_set_inner_html: "{highlighted_html}",
                style: "width: 100%; height: 200px; border: 1px solid #ccc; padding: 10px; overflow-y: auto;"
            }
        }
    })
}

fn highlight_text(text: &str, query: &str) -> String {
    if query.is_empty() {
        return text.to_string();
    }
    let highlighted = text.replace(
        query,
        &format!("{}", query),
    );
    highlighted
}
    

CSS 样式


.editable-area {
    border: 1px solid #ccc;
    padding: 10px;
    min-height: 100px;
    overflow-y: auto;
}

.highlight {
    background-color: yellow;
    color: black;
}
    

优点

  • 支持部分文本样式化,能够实现高亮效果。
  • 灵活性高,可以自定义复杂的样式和行为。

缺点

  • 需要手动处理光标位置,避免更新内容时光标跳动。
  • 对大文本内容的处理可能影响性能。
  • 在不同平台和浏览器上,contenteditable 的行为可能有所不同,需进行兼容性测试。

解决方案二:使用 textarea 和覆盖层

如果必须使用原生的 textarea,可以通过创建一个覆盖层的 div 来实现高亮效果。具体思路是,在 textarea 上方或下方放置一个透明的 textarea,并在其背后使用一个同步内容的 div 来显示高亮文本。

原理

通过将一个 div 叠加在 textarea 上,并同步其内容,可以在 div 中实现高亮效果,而保持用户在 textarea 中的原生输入体验。这种方法将高亮和输入分离,既保留了 textarea 的功能,又实现了高亮效果。

实现步骤

  1. 创建同步的 divtextarea

    在同一个容器中放置一个 div 和一个透明的 textarea,使它们重叠显示。

  2. 同步文本内容:

    监听 textarea 的输入事件,并将内容同步到覆盖层的 div 中。

  3. 应用高亮逻辑:

    div 中根据搜索关键词动态插入高亮样式。

示例代码


use dioxus::prelude::*;

fn main() {
    dioxus::desktop::launch(App);
}

fn App(cx: Scope) -> Element {
    let text = use_state(cx, || String::new());
    let search_query = use_state(cx, || String::new());

    let highlighted_html = highlight_text(&text, &search_query);

    cx.render(rsx! {
        div {
            style: "position: relative; font-family: monospace;",
            input {
                type: "text",
                placeholder: "输入搜索关键词",
                value: "{search_query}",
                oninput: move |e| search_query.set(e.value.clone()),
                style: "margin-bottom: 10px; width: 100%; padding: 8px;"
            }
            div {
                style: "position: relative; width: 100%; height: 200px;",
                div {
                    id: "highlight-layer",
                    dangerously_set_inner_html: "{highlighted_html}",
                    style: "position: absolute; top: 0; left: 0; width: 100%; height: 100%; 
                            pointer-events: none; color: transparent; white-space: pre-wrap; 
                            overflow-wrap: break-word; background: transparent; padding: 10px; 
                            box-sizing: border-box;"
                }
                textarea {
                    value: "{text}",
                    oninput: move |e| text.set(e.value.clone()),
                    style: "position: absolute; top: 0; left: 0; width: 100%; height: 100%; 
                            background: transparent; color: black; border: 1px solid #ccc; 
                            padding: 10px; box-sizing: border-box; resize: none; 
                            overflow-y: auto; caret-color: black;"
                }
            }
        }
    })
}

fn highlight_text(text: &str, query: &str) -> String {
    if query.is_empty() {
        return html_escape::encode_text(text).to_string();
    }
    let replaced = html_escape::encode_text(text).replace(
        query,
        &format!("{}", query),
    );
    replaced
}
    

CSS 样式


.textarea-wrapper {
    position: relative;
    width: 100%;
    height: 200px;
}

.highlight-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    color: transparent;
    white-space: pre-wrap;
    overflow-wrap: break-word;
    pointer-events: none;
    z-index: 1;
}

textarea {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: transparent;
    z-index: 2;
    color: black;
    caret-color: black;
    border: 1px solid #ccc;
    padding: 10px;
    resize: none;
}
    

优点

  • 保留了 textarea 的原生输入体验。
  • 高亮逻辑和用户输入分离,易于维护。

缺点

  • 需要同步两个层的内容,增加了实现复杂性。
  • 在高亮逻辑复杂或文本量较大时,可能影响性能。

解决方案三:使用第三方库或自定义渲染器

如果您的项目需要更复杂的高亮功能,如语法高亮、智能提示等,可以考虑使用现有的第三方库,例如 CodeMirrorMonaco Editor。这些库提供了丰富的功能和高度的可定制性,可以满足复杂的需求。

使用第三方库的优势

  • 功能强大,支持多种高亮需求。
  • 社区支持丰富,文档齐全。
  • 易于集成和扩展。

实现步骤

  1. 集成第三方库:

    将选定的第三方库集成到 Dioxus 项目中,通常需要通过 WebView 加载相应的 HTML 和 JavaScript 代码。

  2. 配置高亮功能:

    根据需求,通过库的 API 配置搜索高亮逻辑。例如,使用 CodeMirror 的搜索扩展功能来实现。

  3. 与 Dioxus 交互:

    通过 Dioxus 的 WebView API 与 JavaScript 代码进行交互,实现数据的双向同步。

示例代码

以下是一个将 CodeMirror 集成到 Dioxus 应用中的简单示例:


use dioxus::prelude::*;
use dioxus_desktop::tao::window::WindowBuilder;
use dioxus_desktop::Config;

fn main() {
    dioxus::desktop::launch_cfg(App, Config::default());
}

fn App(cx: Scope) -> Element {
    cx.render(rsx! {
        div {
            id: "editor",
            style: "height: 200px; border: 1px solid #ccc;"
        }
        script { src: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js" }
        link { rel: "stylesheet", href: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css" }
        script { 
            dangerously_set_inner_html: "{`
                const editor = CodeMirror(document.getElementById('editor'), {
                    lineNumbers: true,
                    mode: 'javascript',
                    theme: 'default'
                });

                function highlightSearch(query) {
                    editor.getAllMarks().forEach(mark => mark.clear());
                    if (query) {
                        const regex = new RegExp(query, 'gi');
                        editor.eachLine(line => {
                            const text = line.text;
                            let match;
                            while ((match = regex.exec(text)) !== null) {
                                editor.markText(
                                    { line: editor.getLineNumber(line), ch: match.index },
                                    { line: editor.getLineNumber(line), ch: match.index + query.length },
                                    { className: 'highlight' }
                                );
                            }
                        });
                    }
                }

                window.highlightSearch = highlightSearch;
            `}"
        }
        style { 
            dangerously_set_inner_html: "{`
                .highlight {
                    background-color: yellow;
                }
            `}"
        }
        input {
            type: "text",
            placeholder: "输入搜索关键词",
            oninput: move |e| {
                let query = e.value.clone();
                // 调用 JavaScript 函数进行高亮
                dioxus_desktop::tao::webview::execute_script(&format!("highlightSearch('{}')", query));
            },
            style: "margin-top: 10px; width: 100%; padding: 8px;"
        }
    })
}
    

优点

  • 功能丰富,支持多种高级特性,如语法高亮、自动补全等。
  • 社区活跃,维护频繁,易于获取支持和更新。
  • 高度可定制,适应不同需求。

缺点

  • 增加了项目的依赖和体积。
  • 可能需要一定的学习成本,尤其是对于不熟悉 JavaScript 的开发者。
  • 复杂的集成过程,需要处理 Rust 与 JavaScript 之间的交互。

解决方案四:结合 WebAssembly 和 JavaScript

为了提高性能和实现更复杂的文本处理,可以结合 WebAssembly(Wasm)和 JavaScript 来实现搜索高亮功能。使用 Rust 编写高效的文本解析器,并通过 wasm-bindgen 将其导出为 WebAssembly 模块,再在 Dioxus 中调用这些模块进行高亮处理。

原理

通过 WebAssembly,可以利用 Rust 的高性能计算能力来处理大文本内容的高亮逻辑。Wasm 模块可以被 JavaScript 调用,将处理结果返回给 Dioxus 进行渲染。

实现步骤

  1. 编写 Rust 解析器并导出为 WebAssembly:

    使用 wasm-bindgen 将 Rust 函数导出为 JavaScript 可调用的模块。

  2. 在 Dioxus 中集成 WebAssembly 模块:

    通过 JavaScript 调用 WebAssembly 模块,并将高亮处理后的内容渲染到界面上。

  3. 实现高亮逻辑:

    在 Rust 中实现高效的文本搜索和高亮算法,将匹配的关键词包裹在 <span> 标签中。

示例代码

以下是一个简单的 Rust 高亮函数示例,并在 Dioxus 中调用该函数:


// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn highlight_text(input: &str, query: &str) -> String {
    if query.is_empty() {
        return input.to_string();
    }
    input.replace(
        query,
        &format!("{}", query),
    )
}
    

然后在 Dioxus 中调用:


use dioxus::prelude::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::Window;

#[wasm_bindgen(module = "/path/to/your/wasm.js")]
extern "C" {
    fn highlight_text(input: &str, query: &str) -> String;
}

fn main() {
    dioxus::desktop::launch(App);
}

fn App(cx: Scope) -> Element {
    let text = use_state(cx, || String::new());
    let search_query = use_state(cx, || String::new());

    let highlighted_html = highlight_text(&text, &search_query);

    cx.render(rsx! {
        div {
            style: "font-family: monospace; padding: 20px;",
            input {
                type: "text",
                placeholder: "输入搜索关键词",
                value: "{search_query}",
                oninput: move |e| search_query.set(e.value.clone()),
                style: "margin-bottom: 10px; width: 100%; padding: 8px;"
            }
            div {
                contenteditable: "true",
                oninput: move |e| text.set(e.value.clone()),
                dangerously_set_inner_html: "{highlighted_html}",
                style: "width: 100%; height: 200px; border: 1px solid #ccc; padding: 10px; overflow-y: auto;"
            }
        }
    })
}
    

优点

  • 高性能,适合处理大文本内容。
  • 充分利用 Rust 的计算能力,提升整体应用性能。

缺点

  • 需要掌握 WebAssembly 和 JavaScript 的交互,增加了开发复杂度。
  • 调试和维护可能更加复杂。

其他注意事项

性能优化

在处理大文本内容时,高亮逻辑可能会成为性能瓶颈。可以考虑以下优化措施:

  • **分块处理**:将文本分割成小块,逐步处理和渲染,避免一次性加载过多内容。
  • **节流和防抖**:限制高亮逻辑的触发频率,减少不必要的计算。
  • **缓存结果**:对已处理的文本片段进行缓存,避免重复计算。

光标位置管理

在动态更新内容时,光标位置可能会丢失或跳动,影响用户体验。可以通过以下方法进行管理:

  • **保存和恢复光标位置**:在更新内容前保存当前光标位置,更新后恢复光标位置。
  • **最小化 DOM 操作**:尽量减少对 DOM 的大规模更新,保持用户输入的连贯性。

用户体验

确保高亮功能的响应速度和准确性,以提升用户体验。以下是一些建议:

  • **实时高亮**:高亮应随用户输入实时更新,避免延迟。
  • **清晰的高亮样式**:选择对比度高且不干扰阅读的颜色进行高亮。
  • **可访问性**:确保高亮文本对所有用户友好,包括色盲用户。

无障碍支持

在实现高亮功能时,应遵循无障碍设计原则:

  • **语义化 HTML**:使用语义化标签,如 <span>,以便辅助技术正确识别高亮内容。
  • **键盘导航**:确保用户可以通过键盘进行搜索和导航。
  • **颜色对比**:选择具有足够对比度的颜色,确保高亮文本易于识别。

总结

在 Dioxus 0.6 框架下实现桌面应用的搜索高亮效果,尽管 textarea 无法直接支持部分文本的 CSS 渲染,但通过以下几种替代方法,可以有效实现类似的功能:

  • **使用 contenteditablediv 替代 textarea**:灵活性高,但需要处理光标位置和性能优化。
  • **使用 textarea 和覆盖层**:保留原生输入体验,适合简单场景,但实现较为复杂。
  • **使用第三方库或自定义渲染器**:功能强大,适用于复杂需求,但增加了项目依赖和集成复杂度。
  • **结合 WebAssembly 和 JavaScript**:高性能,适合大文本处理,但开发复杂度高。

选择合适的实现方案,应根据项目的具体需求和开发团队的技术栈熟悉程度进行权衡。希望本文提供的解决方案和示例代码,能够帮助你在 Dioxus 框架下顺利实现搜索高亮功能。

参考链接

dioxuslabs.com
Dioxus 官方文档
doc.rust-lang.org
Rust 和 WebAssembly
codemirror.net
CodeMirror 官网
microsoft.github.io
Monaco Editor 官网
highlightjs.org
highlight.js

Last updated January 3, 2025
Ask Ithy AI
Download Article
Delete Article