js如何实现一个简单的富文本编辑器

964 阅读1分钟

简要说明

document.execCommand来对可编辑的元素进行操作 详细的execCommand的使用移步至:execCommand文档

selection 和 range 防止失焦无法操作 selection文档

采用技术栈

  • webpack
  • typescript

目录结构

配置webpack

#### webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/,
      },
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader'],
        exclude: /node_modules/
      }
    ],
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
  },
  output: {
    filename: 'kEditor.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: 'index.html'
    })
  ]
};

实现代码,利用document.execCommand

#### 实现
import { createElement } from "./utils";

import "./index.less";

class Editor {
  private options: Editor.options;
  private dom: HTMLDivElement | null;
  private selection: Selection | null | undefined;
  private range: Range | undefined;

  constructor(options: Editor.options) {
    this.options = options;
    const { el } = options;
    this.dom = document.querySelector(el);
    this.selection = window.getSelection();
    this.init();
  }

  attr(key: string, value: string) {
    this.dom?.setAttribute(key, value);
  }

  exec(cmd: string, showUI: boolean = false, value: string = "") {
    document.execCommand(cmd, showUI, value);
  }

  setRange() {
    const { selection } = this;
    if (selection && !selection?.isCollapsed) {
      const range = selection?.getRangeAt(0);
      console.log("selection", selection?.toString(), selection, range);
      this.range = range;
    }
  }

  recoverRange() {
    const { selection, range } = this;

    if (selection && range) {
      if (selection.rangeCount > 0) selection.removeAllRanges();
      selection?.addRange(range.cloneRange());
    }
  }

  init() {
    const that = this;

    that.attr("contenteditable", "true");
    that.attr("class", "KEditor");

    that.dom?.before(
      createElement("button", "bold", that.setBold.bind(that)),
      createElement("button", "color", that.setColor.bind(that)),
      createElement("button", "bgColor", that.setBgColor.bind(that)),
      createElement("button", "alertHtml", that.getHtml.bind(that)),
      createElement("button", "alertText", that.getText.bind(that))
    );

    //
    that.dom?.addEventListener("blur", () => {
      that.setRange();
    });
  }

  setBold() {
    this.recoverRange();
    this.exec("bold");
  }

  setColor() {
    this.recoverRange();
    const color = prompt("请输入颜色") ?? "#000";
    this.exec("foreColor", false, color);
  }

  setBgColor() {
    this.recoverRange();
    const color = prompt("请输入颜色") ?? "#000";
    this.exec("backColor", true, color);
    console.log("setBgColor", color);
  }

  getHtml() {
    const html = this.dom?.innerHTML;
    alert(html);
    return html;
  }

  getText() {
    const text = this.dom?.innerText;
    alert(text);
    return text;
  }
}

const editor = new Editor({
  el: "#KEditor",
});

创建一个可点击的工具按钮

/**
 *  创建一个编辑器工具按钮
 * @param tagName createToolbutton
 */
export const createElement = (
  tagName: string,
  text: string,
  click: Function
): HTMLElement => {
  const element = document.createElement(tagName);
  element.onclick = function (this: GlobalEventHandlers, ev: MouseEvent) {
    click && click(this);
  };
  element.innerText = text;
  return element;
};

项目源码地址

实现一个简单的富文本编辑器