Quill 富文本编辑器实现自定义font-size

399 阅读3分钟

安装使用

$> npm i quill

创建quill实例,加载基本的样式、设置主题snow

<template>
  <div class="rich-editor">
    <div ref="root"></div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import Quill from "quill";
// 核心样式
// import "quill/dist/quill.core.css";
// 主题
import "quill/dist/quill.snow.css";

defineOptions({
  name: "RichEditor",
});

const root = ref<HTMLElement>();
let editor = null;

onMounted(() => {
  editor = new Quill(root.value!, {
    modules: { toolbar: true },
    theme: "snow",
  });
});
</script>

这是基础的完整代码,之后的更改不再贴重复代码。

效果如下,提供了主题snow,如果我们需要自定义主题,则只需要加载核心样式import "quill/dist/quill.core.css";然后自定义设置其他样式。

quill.png

quill 已经提供了两种主题:snowbubble。上面已经展示主题snow,bubble是基于弹出提示的主题

import "quill/dist/quill.bubble.css";

editor = new Quill(root.value!, {
  theme: "bubble",
});

工具栏不会直接展示在页面上,当你选中文本时弹出操作。双击时也会触发,因为编辑器内容末尾默认有一个\n会被选中,从而实现交互。

quill-bubble.png

配置工具栏

主题的定制更多是对工具栏的定义以及节点样式的调整。我们可以通过自定义modules.toolbar来定义工具栏;定义css来定义节点DOM的样式。自定义主题时,我们需要加载基本的样式

创建工具栏,我们可以在snow主题基础上去配置,两种方式:一是配置定义toolbar需要哪些内容格式的功能,罗列出来;还可以指定DOM节点,DOM元素内使用已经注册过的format,也可以不用注册,自己管理DOM样式、事件,并通过API调用将自己处理过的格式应用到内容。

editor = new Quill(root.value!, {
  modules: {
    toolbar: [
      { font: [] },
      { size: [] },
      "bold",
      "italic",
      { list: "ordered" },
      { list: "bullet" },
      { list: "check" },
      { align: [] },
      { background: [] },
      { color: [] },

      "table",
    ],
  },
  theme: "snow",
});

系统提供的formats类型不一样,使用方式也不一样。对于一些属性bold \ italic语义是与否,则直接罗列出来即可;而对于有选择的格式font \ size等都需要配置选择的值。

要知道某个格式该怎么配置,区分节点Blots还是属性Attributor的实现,这是Parchment提供的来个两种数据模型,实现方式不同,使用方式也不一致。通常属性Attributor都是多值配置,而Blots则取决于代码实现。

比如加粗bold,继承自Inline并重写了一些方法。静态方法formats的实现标识它的值为boolean类型。

import Inline from '../blots/inline.js';
class Bold extends Inline {
  static blotName = 'bold';
  static tagName = ['STRONG', 'B'];
  static create() {
    return super.create();
  }
  static formats() {
    return true;
  }
  optimize(context) {
    super.optimize(context);
    if (this.domNode.tagName !== this.statics.tagName[0]) {
      this.replaceWith(this.statics.blotName);
    }
  }
}
export default Bold;

自定义size

quill提供了这些格式默认的值,如果我们想自定义提供了一些值,比如size替换掉原来的值.

editor = new Quill(root.value!, {
  modules: {
    toolbar: [
      { size: ['12px','14px','16px','18px'] },
    ],
  },
  theme: "snow",
});

改过之后发现下拉展示的内容并不对,但是查看元素时元素的data-value绑定的值是对的,查看原来主题的css时,可以看到snow主题是覆盖样式写的,那我们也加一下。

.ql-snow {
  .ql-picker.ql-size {
    .ql-picker-label::before {
      content: attr(data-value);
    }
    .ql-picker-item {
      &::before {
        content: attr(data-value);
      }
      &[data-value="12px"]::before {
        font-size: 12px;
      }
      &[data-value="14px"]::before {
        font-size: 14px;
      }
      &[data-value="16px"]::before {
        font-size: 16px;
      }
      &[data-value="18px"]::before {
        font-size: 18px;
      }
    }
  }
}

改完之后显示正常了,也可以正常切换了。但是在输入后切换字号,内容并没有追加对应的class,按照逻辑不同的字号会有classql-size-${value}这样的。

这里再处理完主题定义的默认值之外,我们去查看formats/size的实现,发现它这里也定义了属性值。

quill-formats-size.png

好家伙,为啥要设置两次,不能从这里取吗。

为什么主题设置了到这里不生效,是因为Parchment.Attributor定义了属性whitelist不在其中的则会被忽略。

还好这里是两个实例,我们导入后进行修改。quill要求不要直接修改导入的对象,会全局影响,一种做法是我们也创建相同的两个实例,把它覆盖掉,或者你不在意就直接修改吧。最后需要Quill.registerister保证生效,不然会有输入上的问题。

import { SizeClass, SizeStyle } from "quill/formats/size";

SizeClass.whitelist = ["12px", "14px", "16px", "18px"];
SizeStyle.whitelist = ["12px", "14px", "16px", "18px"];

Quill.register(SizeStyle, true);
Quill.register(SizeClass, true);

修改完成后,再次进行编辑,可以看到生成的DOM结构是正常,我们还需自己书写这些class的样式。

quill-size-dom.png

.ql-snow{
  .ql-editor {
    .ql-size-12px {
      font-size: 12px;
    }
    .ql-size-14px {
      font-size: 14px;
    }
    .ql-size-16px {
      font-size: 16px;
    }
    .ql-size-18px {
      font-size: 18px;
    }
  }
}

好了,这样输入可以看到没有问题,但是我们自定义字体并没有设置默认值,也就是可以上图中第一行你好!是随着系统默认大小,并不是12px。可以初始化一个字号作为默认字体大小。

editor!.format("size", "12px");

但是如果你初始设置了14px,初始默认显示还是12px,当你输入后就会跳显示为14px。这种配置方式没有发现怎么去设置默认值,但是可以通过另一种配置工具栏绑定DOM的方式解决。

<template>
  <div class="rich-editor">
    <div ref="toolbar">
      <select class="ql-size">
        <option value="12px">12px</option>
        <option value="14px" selected>14px</option>
        <option value="16px">16px</option>
        <option value="18px">18px</option>
      </select>

      <button class="ql-bold"></button>
    </div>
    <div ref="root"></div>
  </div>
</template>
<script setup lang="ts">

const root = ref<HTMLElement>();
const toolbar = ref<HTMLElement>();

onMounted(() => {
  editor = new Quill(root.value!, {
    modules: {
      toolbar: {
        container: toolbar.value!,
      },
    },
    theme: "snow",
  });
})
</script>

quill-custom-size.png

完美实现

这样就完美了,可以看到初始显示就是正常的。其他format可以通过buttonvalue属性定义默认值;select则可以通过optionselected设置默认值。

当然了,如果我们就想初始跟随系统字号,那么需要在定义值中加值为false的选项,quill会添加一个空的option表示,然后需要我们定义calss追加默认的显示名称,比如Normal

editor = new Quill(root.value!, {
  modules: {
    toolbar: [
      { size: ['12px',false,'14px','16px','18px'] },
    ],
  },
  theme: "snow",
});

这时工具栏上已经默认展示了值false的空options,但是由于它没有具体的值,之前设置的attr(data-value)取不到值。进行调整修改,给一个默认的值

.ql-snow {
  .ql-picker.ql-size {
    .ql-picker-label {
      &::before {
        content: "Normal";
      }
      &[data-value="12px"]::before {
        content: "12px";
      }
      &[data-value="14px"]::before {
        content: "14px";
      }
      &[data-value="16px"]::before {
        content: "16px";
      }
      &[data-value="18px"]::before {
        content: "18px";
      }
    }
    .ql-picker-item {
      &::before {
        content: "Normal";
      }
      &[data-value="12px"]::before {
        content: "12px";
        font-size: 12px;
      }
      &[data-value="14px"]::before {
        content: "14px";
        font-size: 14px;
      }
      &[data-value="16px"]::before {
        content: "16px";
        font-size: 16px;
      }
      &[data-value="18px"]::before {
        content: "18px";
        font-size: 18px;
      }
    }
  }
}

这样就是书写的样式多了点,如果使用了一些其他的css库,可以动态生成的就能方便很多。比如使用了less,通过定义字体数组变量循环生成。

.ql-snow {
  @font-sizes: 12px, 14px, 16px, 18px;
  // 默认
  @font-default: "Normal";

  .generate-font-size(@sizes,@set-font:false) {
    each(@sizes,{
      @value: extract(@sizes,@index);
      &[data-value="@{value}"]::before{
        content:"@{value}"
      } 
      &[data-value="@{value}"]::before when (@set-font){
        font-size:@value;
      }
    });
  }

  .ql-editor {
    each(@font-sizes,{
      @value: extract(@font-sizes,@index);

      .ql-size-@{value} {
        font-size: @value;
      }
    });
  }
  .ql-picker.ql-size {
    .ql-picker-label,
    .ql-picker-item {
      &::before {
        content: @font-default;
      }
    }
    .ql-picker-label {
      .generate-font-size( @font-sizes);
    }
    .ql-picker-item {
      .generate-font-size( @font-sizes,true);
    }
  }
}

ok,搞定了,这样针对其他默认的格式,如果我们需要自定义值,按照此方法进行调整即可。

😀enjoy it!