基于 quill 实现的富文本编辑器
# 图例

# props
- value
- 富文本编辑器的内容,这个内容是经过 quill 转换过的,并不是直接可读的内容
- onChange
- theme
- 主题色,默认 'snow', 代表白色
- placeholder
- readOnlyreadOnly
- readOnly 只读
- bounds
- 挂载的 dom 元素
- modules
- quill 富文本编辑器的配置
- className
# 实现代码
点击 展开/收起 jsx 代码
/**
* @description 自定义 QuillTextEditor 组件
*/
import React, { useEffect, useRef } from 'react';
import classnames from 'classnames';
import Quill, { RangeStatic } from 'quill';
import { isDelta, isValuesEqual } from './utils';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
const QuillTextEditor = (props) => {
const {
theme = 'snow',
className,
value,
onChange,
bounds,
modules,
placeholder = '请输入',
readOnly,
} = props;
// 创建一个ref来存储editor的DOM元素
const contanierRef = useRef(null);
// 存储Quill实例的ref
const quillRef = useRef(null);
// 设置编辑器的内容,但不触发onChange
const silentUpdate = (content) => {
const editor = quillRef.current;
if (isDelta(value)) {
editor.setContents(value);
} else if (typeof value === 'string') {
editor.setContents(editor.clipboard.convert(content));
}
};
useEffect(() => {
if (contanierRef.current) {
const editor = new Quill(contanierRef.current, {
theme,
modules,
placeholder,
readOnly: false,
bounds,
});
quillRef.current = editor;
// 初始加载时设置内容
silentUpdate(value);
// 注册编辑器的变更回调函数
const handleChange = () => {
const currentContent = editor.root.innerHTML;
onChange?.(currentContent);
};
// 注册 编辑器的改变回调函数 调用传入的onChange回调函数,传递当前内容
editor.on('text-change', handleChange);
return () => {
editor.off('text-change', handleChange);
};
}
}, []);
// 当外部控制的value改变时,更新编辑器内容
useEffect(() => {
if (quillRef.current && !isValuesEqual(value, getEditorInnerHtml())) {
const selection = quillRef.current.getSelection(); // 保存当前的选择区域
quillRef.current.setContents(
quillRef.current.clipboard.convert(value),
'silent',
);
if (selection) {
// 如果有选区,恢复选区
quillRef.current.setSelection(selection);
}
}
}, [value]);
useEffect(() => {
if (quillRef.current) {
if (readOnly) {
quillRef.current?.disable();
} else {
quillRef.current?.enable();
}
}
}, [readOnly]);
// 获取编辑器内部 html 元素内容
const getEditorInnerHtml = () => {
return quillRef.current?.root.innerHTML;
};
return (
<div
className={classnames('quill-text-editor ql-editor-container', className)}
>
<div ref={contanierRef} />
</div>
);
};
export default QuillTextEditor;
/**
* 使用示例
*/
const test = () => {
return (
<QuillTextEditor
className="day-book-quill"
value={text}
onChange={setText}
readOnly={readOnly}
bounds={`#id .ql-editor-container`}
modules={{
// syntax: true,
toolbar: {
container: [
// [{ 'header': 1 }, { 'header': 2 }], // 标题 —— 独立平铺
// [{header: [1, 2, 3, 4, 5, 6, false]}], // 标题 —— 下拉选择
// [{size: ["small", false, "large", "huge"]}], // 字体大小
[{ list: 'ordered' }, { list: 'bullet' }], // 有序、无序列表
// ["blockquote", "code-block"], // 引用 代码块
['blockquote'], // 引用 代码块
// 链接按钮需选中文字后点击
// ["link", "image", "video"], // 链接、图片、视频
['link', 'image'], // 链接、图片、视频
[{ align: [] }], // 对齐方式// text direction
// [{indent: "-1"}, {indent: "+1"}], // 缩进
// ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
['bold', 'underline', 'strike'], // 加粗 斜体 下划线 删除线
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
// [{'script': 'sub'}, {'script': 'super'}], // 下标/上标
// [{'font': []}],//字体
['clean'], // 清除文本格式
],
},
}}
/>
);
};
点击 展开/收起 css 代码
.quill-text-editor.ql-editor-container {
.ql-toolbar {
padding: 6px 8px;
.ql-formats {
margin-right: 36px;
button {
.ql-stroke {
stroke: var(--black45-opcity1);
}
&:hover {
color: var(--brand);
.ql-stroke {
stroke: var(--brand);
}
}
&.ql-link::after {
border-right: 0;
padding-left: 6px;
content: '链接';
}
&.ql-clean::after {
border-right: 0;
padding-left: 6px;
content: '清空样式';
}
}
}
}
.ql-container {
.ql-editor {
font-size: 12px;
a {
color: var(--brand);
}
&::before {
font-style: normal;
color: var(--black45);
font-size: 12px;
}
}
.ql-tooltip input[data-link]::placeholder {
opacity: 0;
font-style: normal;
}
.ql-tooltip[data-mode='link']::before {
content: '网址:';
color: var(--black45);
}
.ql-preview {
color: var(--brand);
}
.ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: '保存';
padding-right: 0;
color: var(--brand);
}
.ql-tooltip::before {
content: '网址:';
line-height: 26px;
margin-right: 8px;
color: var(--black45);
}
.ql-tooltip a.ql-action::after {
border-right: 1px solid var(--grey);
content: '编辑';
margin-left: 16px;
padding-right: 8px;
color: var(--brand);
}
.ql-tooltip a.ql-remove::before {
content: '移除';
margin-left: 8px;
color: var(--brand);
}
}
}
点击 展开/收起 配置变量 代码
/**
* @des 一些变量
*/
const titleConfig = [
{ Choice: '.ql-bold', title: '加粗' },
{ Choice: '.ql-italic', title: '斜体' },
{ Choice: '.ql-underline', title: '下划线' },
{ Choice: '.ql-header', title: '段落格式' },
{ Choice: '.ql-strike', title: '删除线' },
{ Choice: '.ql-blockquote', title: '块引用' },
{ Choice: '.ql-code', title: '插入代码' },
{ Choice: '.ql-code-block', title: '插入代码段' },
{ Choice: '.ql-font', title: '字体' },
{ Choice: '.ql-size', title: '字体大小' },
{ Choice: '.ql-list[value="ordered"]', title: '编号列表' },
{ Choice: '.ql-list[value="bullet"]', title: '项目列表' },
{ Choice: '.ql-direction', title: '文本方向' },
{ Choice: '.ql-header[value="1"]', title: 'h1' },
{ Choice: '.ql-header[value="2"]', title: 'h2' },
{ Choice: '.ql-align', title: '对齐方式' },
{ Choice: '.ql-color', title: '字体颜色' },
{ Choice: '.ql-background', title: '背景颜色' },
{ Choice: '.ql-image', title: '图像' },
{ Choice: '.ql-video', title: '视频' },
{ Choice: '.ql-link', title: '添加链接' },
{ Choice: '.ql-formula', title: '插入公式' },
{ Choice: '.ql-clean', title: '清除字体格式' },
{ Choice: '.ql-script[value="sub"]', title: '下标' },
{ Choice: '.ql-script[value="super"]', title: '上标' },
{ Choice: '.ql-indent[value="-1"]', title: '向左缩进' },
{ Choice: '.ql-indent[value="+1"]', title: '向右缩进' },
{ Choice: '.ql-header .ql-picker-label', title: '标题大小' },
{ Choice: '.ql-header .ql-picker-item[data-value="1"]', title: '标题一' },
{ Choice: '.ql-header .ql-picker-item[data-value="2"]', title: '标题二' },
{ Choice: '.ql-header .ql-picker-item[data-value="3"]', title: '标题三' },
{ Choice: '.ql-header .ql-picker-item[data-value="4"]', title: '标题四' },
{ Choice: '.ql-header .ql-picker-item[data-value="5"]', title: '标题五' },
{ Choice: '.ql-header .ql-picker-item[data-value="6"]', title: '标题六' },
{ Choice: '.ql-header .ql-picker-item:last-child', title: '标准' },
{ Choice: '.ql-size .ql-picker-item[data-value="small"]', title: '小号' },
{ Choice: '.ql-size .ql-picker-item[data-value="large"]', title: '大号' },
{ Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: '超大号' },
{ Choice: '.ql-size .ql-picker-item:nth-child(2)', title: '标准' },
{ Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐' },
{
Choice: '.ql-align .ql-picker-item[data-value="center"]',
title: '居中对齐',
},
{
Choice: '.ql-align .ql-picker-item[data-value="right"]',
title: '居右对齐',
},
{
Choice: '.ql-align .ql-picker-item[data-value="justify"]',
title: '两端对齐',
},
];