纯JavaScript实现文本选择工具栏:功能详解与源码解析

示例展示:

在这里插入图片描述

其实核心是在网页中获取用户鼠标选中的文本,通过 JavaScript 的 window.getSelection() 方法来实现。这个方法返回一个 Selection 对象,该对象包含了关于选中文本的信息,最后有贴上完整代码。

功能概述

  1. 富文本编辑功能:用户可以在编辑区域输入和格式化文本

  2. 智能工具栏:当用户选中文本时,会自动弹出浮动工具栏

  3. 格式操作

    • 文本加粗/斜体/下划线
    • 高亮标记
    • 文字颜色修改
    • 复制文本
    • 撤销/重做操作
  4. 历史记录:支持撤销(ctrl+z)和重做(ctrl+y)功能

  5. 响应式设计:适配不同屏幕尺寸

逻辑代码:

<script>
      document.addEventListener("DOMContentLoaded", () => {
   
   
        const textEditor = document.getElementById("textEditor");
        const textToolbar = document.getElementById("textToolbar");
        const btnBold = document.getElementById("btnBold");
        const btnItalic = document.getElementById("btnItalic");
        const btnUnderline = document.getElementById("btnUnderline");
        const btnHighlight = document.getElementById("btnHighlight");
        const colorPicker = document.getElementById("colorPicker");
        const btnCopy = document.getElementById("btnCopy");
        const btnUndo = document.getElementById("btnUndo");
        const btnRedo = document.getElementById("btnRedo");

        // 操作历史记录
        const history = {
   
   
          states: [],
          currentIndex: -1,
        };

        // 保存编辑器状态
        function saveState() {
   
   
          const html = textEditor.innerHTML;

          // 避免保存相同状态
          if (history.states[history.currentIndex] === html) return;

          // 移除当前索引之后的状态
          history.states = history.states.slice(0, history.currentIndex + 1);
          history.states.push(html);
          history.currentIndex++;
        }

        // 初始化状态
        saveState();

        // 显示工具栏
        function showToolbar() {
   
   
          const selection = window.getSelection();
          if (!selection.toString().trim()) {
   
   
            textToolbar.style.display = "none";
            return;
          }

          const range = selection.getRangeAt(0);
          const rect = range.getBoundingClientRect();
          const editorRect = textEditor.getBoundingClientRect();

          // 计算工具栏位置
          let top = rect.top + window.scrollY - textToolbar.offsetHeight - 8;
          let left =
            rect.left +
            window.scrollX +
            rect.width / 2 -
            textToolbar.offsetWidth / 2;

          // 边界检查 - 确保工具栏在可视区域内
          if (top < window.scrollY) {
   
   
            top = rect.bottom + window.scrollY + 8;
          }

          if (left < editorRect.left) {
   
   
            left = editorRect.left + 10;
          } else if (left + textToolbar.offsetWidth > editorRect.right) {
   
   
            left = editorRect.right - textToolbar.offsetWidth - 10;
          }

          textToolbar.style.display = "flex";
          textToolbar.style.top = top + "px";
          textToolbar.style.left = left + "px";
        }

        // 隐藏工具栏
        function hideToolbar() {
   
   
          textToolbar.style.display = "none";
        }

        // 执行格式命令
        function formatCommand(command, value = null) {
   
   
          saveState();
          document.execCommand(command, false, value);
          textEditor.focus();
        }

        // 高亮文本
        function toggleHighlight() {
   
   
          saveState();
          if (document.queryCommandState("hiliteColor")) {
   
   
            document.execCommand("hiliteColor", false, "transparent");
          } else {
   
   
            document.execCommand("hiliteColor", false, "#FFFF00");
          }
          textEditor.focus();
        }

        // 复制文本
        function copyText() {
   
   
          const selection = window.getSelection();
          navigator.clipboard
            .writeText(selection.toString())
            .then(() => {
   
   
              btnCopy.innerHTML = '<i class="fas fa-check"></i>';
              setTimeout(() => {
   
   
                btnCopy.innerHTML = '<i class="fas fa-copy"></i>';
              }, 1500);
            })
            .catch((err) => {
   
   
              console.error("复制失败:", err);
            });
          textEditor.focus();
        }

        // 撤销操作
        function undo() {
   
   
          if (history.currentIndex > 0) {
   
   
            history.currentIndex--;
            textEditor.innerHTML = history.states[history.currentIndex];
          }
          textEditor.focus();
        }

        // 重做操作
        function redo() {
   
   
          if (history.currentIndex < history.states.length - 1) {
   
   
            history.currentIndex++;
            textEditor.innerHTML = history.states[history.currentIndex];
          }
          textEditor.focus();
        }

        // 事件监听
        textEditor.addEventListener("mouseup", showToolbar);
        textEditor.addEventListener("keyup", showToolbar);
        textEditor.addEventListener("input", saveState);

        document.addEventListener("mousedown", (e) => {
   
   
          if (!textToolbar.contains(e.target)) {
   
   
            hideToolbar();
          }
        });

        // 加粗
        btnBold.addEventListener("click", () => formatCommand("bold"));
        // 倾斜
        btnItalic.addEventListener("click", () => formatCommand("italic"));
        // 下划线 
        btnUnderline.addEventListener("click", () =>formatCommand("underline"));
        btnHighlight.addEventListener("click", toggleHighlight);

        colorPicker.addEventListener("input", (e) => {
   
   
          formatCommand("foreColor", e.target.value);
        });

        btnCopy.addEventListener("click", copyText);
        btnUndo.addEventListener("click", undo);
        btnRedo.addEventListener("click", redo);

        // 窗口大小变化时重新定位工具栏
        window.addEventListener("resize", () => {
   
   
          if (textToolbar.style.display === "flex") {
   
   
            showToolbar();
          }
        });
      });
    </script>

核心功能实现

1. DOM 加载与初始化
document.addEventListener("DOMContentLoaded", () => {
   
    ... });
  • 确保页面 DOM 完全加载后再执行脚本。
  • 避免因元素未加载导致的 null 错误。
2. 工具栏显示逻辑:showToolbar()

富文本编辑器中的“浮动格式栏”

触发条件

  • mouseup:鼠
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值