[原文地址](www.yuque.com/docs/share/… 《react-quill 富文本编辑器&采坑》)
由于业务功能,需要实现当用户在富文本里进行可以格式化操作,内容粘贴操作的时候,如果用户复制的是图片,需要将图片上传服务器后,插入到文本内;看似合情合理的要求,却有很多坑。
开始使用
安装 react-quill
npm install react-quill @ beta
资源:
github地址
quill 中文文档地址
quill 文档地址
直接上手练练 Codeopen
1、自定义富文本格式刷功能
quill是不具备格式刷的功能,那么也就没有对应的icon,这时就需要去自定义工具栏,quill工具栏控件即可以用一个格式名称数组定义,也可以用一般的HTML容器定义。这里采用的是HTML容器定义。
先自定义一个格式刷的icon。这里的icon采用的是iconfont的unicode引用,支持按字体的方式去动态调整图标大小,颜色等等。可以实现选中高亮的效果,与原来的工具栏交互保持一致。
const CustomButton = () => <i className="iconfont"></i>;
自定义HTML容器CustomToolbar
const CustomToolbar = () => (
<div id="toolbar">
<select className="ql-header" defaultValue={''} onChange={(e) => e.persist()}>
<option value="1" />
<option value="2" />
<option value="3" />
<option value="4" />
<option value="5" />
<option selected />
</select>
<select className="ql-size" defaultValue={''} onChange={(e) => e.persist()}>
<option value="small" />
<option selected />
<option value="large" />
<option value="huge" />
</select>
<button className="ql-formatBrush">
<CustomButton />
</button>
<button className="ql-bold" />
<button className="ql-italic" />
<button className="ql-underline" />
<button className="ql-strike" />
<select className="ql-color" />
<select className="ql-background" />
<select className="ql-align" />
<button className="ql-list" value="ordered" />
<button className="ql-list" value="bullet" />
<button className="ql-indent" value="+1" />
<button className="ql-indent" value="-1" />
<button className="ql-image" />
<button className="ql-clean" />
</div>
);
CustomToolbar就是加入原有的工具栏与格式刷工具栏。把自定义的工具栏放到return里。
return (
<div className={styles['rich-text']}>
<CustomToolbar />
<ReactQuill
ref={editorRef}
placeholder={placeholder || '请输入内容'}
modules={modules}
theme="snow"
{...props}
value={value}
/>
</div>
);
效果:
格式刷功能
接下来是格式刷功能,在modules完成格式刷。formatBrush与工具栏 className="ql-formatBrush"
保持一致。
//记得声明变量
let quillEditor = null;
const copyFormatting = {
value: 'un-active',
format: {},
};
//modules 方法
const modules = useMemo(() => {
return {
toolbar: {
// container: toolbarContainer,
container: '#toolbar',
handlers: {
image: imageHandler,
formatBrush: () => {
copyFormatBrush(quillEditor, copyFormatting);
},
},
},
};
}, []);
关键的格式设置样式方法,点击格式刷,如果选中的区域有样式,则保存样式,格式刷功能为选中状态,再次点击删除样式,取消选中;设置格式样式。可以把这3个方法放到单独文件导出管理。
/* eslint-disable @typescript-eslint/no-use-before-define */
export const copyFormatBrush = (quillEditor, copyFormatting) => {
// 点击格式刷,如果有选中区域且有样式,则保存其样式,按键状态改为选中。
// 再次点击,删除样式,按键取消选中。
if (copyFormatting.value === 'un-active') {
const range = quillEditor.getSelection(true);
if (range == null || range.length === 0) return;
const format = quillEditor.getFormat(range);
if (Object.keys(format).length === 0) return;
setCopyFormatting(quillEditor, copyFormatting, 'active', format);
} else {
setCopyFormatting(quillEditor, copyFormatting, 'un-active', null);
}
};
// 设置copyFormatting: 修改保存的样式、按键状态、粘贴样式的处理程序
export const setCopyFormatting = (quill, copyFormatting, value, format) => {
copyFormatting.value = value;
copyFormatting.format = format;
const toolbar = quill.getModule('toolbar').container;
const brushBtn = toolbar.querySelector('.ql-formatBrush');
if (value === 'active') {
brushBtn.classList.add('ql-formatBrushactive');
quill.on('selection-change', pasteFormatHandler);
} else {
brushBtn.classList.remove('ql-formatBrushactive');
quill.off('selection-change', pasteFormatHandler);
}
function pasteFormatHandler(range, oldRange, source) {
return pasteFormat(range, oldRange, source, quill, copyFormatting);
}
};
// 粘贴样式的处理程序: 如果选中范围且有保存样式,则粘贴样式,并初始化copyFormatting
export const pasteFormat = (range, oldRange, source, quill, copyFormatting) => {
if (range && copyFormatting.format) {
if (range.length === 0) {
} else {
quill.formatText(range.index, range.length + 1, copyFormatting.format);
setCopyFormatting(quill, copyFormatting, 'un-active', null);
}
} else {
// console.log('Cursor not in the editor')
}
};
本人测试使用默认的 ql-active
高亮样式,不起作用,只有自定义格式刷的高亮样式:
.ql-toolbar.ql-snow .ql-formatBrushactive {
color: #06c;
}
效果:
以上格式刷功能已经实现。
2、内容粘贴操作-图片直接预览
用户复制的内容里包含跨域的图片,需要将图片上传服务器后,插入到文本内显示。
目前image功能是自定义封装了,用户插入图片会走antd的Upload上传,把图片资源上传服务器后再插入我们本服务器的URL图片。
自定义Quill的行为和功能
主要方法是imageHandler,使用 Quill.register
模块可以自定义Quill的行为和功能,
const modules = useMemo(() => {
return {
toolbar: {
// container: toolbarContainer,
container: '#toolbar',
handlers: {
image: imageHandler,
},
},
};
}, []);
具体的弹窗逻辑就不过多展示,在拿到上传完的fileList数据,就在富文本插入图片。
//在index.jsx内引入自定义的模块image功能
import './ImageBlot';
// 插入图片
const insertImages = (fileList = []) => {
if (quillEditor && typeof quillEditor.insertEmbed === 'function') {
quillEditor.focus();
const unprivilegedEditor = editorRef.current.makeUnprivilegedEditor(quillEditor);
fileList.forEach((file) => {
setTimeout(() => {
const range = unprivilegedEditor.getSelection();
const position = range ? range.index : 0;
quillEditor.insertEmbed(position, 'image', {
ossid: file.ossId,
url: file.url,
});
quillEditor.setSelection(position + 1, 0);
}, 0);
});
}
};
// return
<ImageUploadModal
onCancel={() => {
setShowImageUpload(false);
}}
onCreate={(fileList) => {
setShowImageUpload(false);
insertImages(fileList);
}}
/>
复制内容图片的上传拿到新的本服务器的url数据也是在自定义image模块内实现。
import { Quill } from 'react-quill';
import { getTmpUrl } from '@/components/AliyunOSSUpload/service';
import { isExpired } from '@/components/AliyunOSSUpload/utils';
import { saveAliOssImg } from '@/services/global';
import errorImg from './error-img.png';
const BlockEmbed = Quill.import('blots/block/embed');
const getExpireFromUrl = (url) => {
const index = url.indexOf('?');
if (index !== -1) {
const reg = new RegExp('(^|&)Expires=([^&]*)(&|$)', 'i');
const str = url.substring(index + 1, url.length);
const expire = str.match(reg)[2];
return expire || 0;
}
return 0;
};
const replacedImage = (ossId, node) => {
getTmpUrl({ ossIds: [ossId] }).then((data) => {
if (data && data.length > 0 && data[0].tmpUrl) {
const url = data[0].tmpUrl;
node.setAttribute('src', url);
}
});
};
/**
* 自定义图片标签,增加ossId参数
*/
class ImageBlot extends BlockEmbed {
static create(value) {
console.log('ImageBlot :>> ', value);
const node = super.create();
if (value.ossid && value.url) {
node.setAttribute('ossid', value.ossid);
node.setAttribute('src', value.url);
const expire = getExpireFromUrl(value.url);
if (expire && isExpired(parseInt(expire, 10))) {
replacedImage(value.ossid, node);
}
}
//对URL图片资源进行装换
if (value.url) {
let sourceType = 'URL';
let content = value.url;
if (value.url.indexOf('base64') > -1) {
sourceType = 'BASE64';
content = value.url.split('base64,')[1];
}
// 跨域图片装换为本服务器资源url
saveAliOssImg({ sourceType, content })
.then((data) => {
if (data && data.url) {
node.setAttribute('src', data.url);
}
})
.catch(() => {
node.setAttribute('src', errorImg);
});
}
console.log('ImageBlot --node-:>> ', node);
return node;
}
static value(node) {
const ossId = node.getAttribute('ossid');
const url = node.getAttribute('src');
return {
ossid: ossId,
url,
};
}
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';
Quill.register(ImageBlot);
实际操作
1、复制域外资源:
2、粘贴富文本效果:
从输出看最后替换结果是没有问题
加入图片加载失败、本地路径图片资源加载失败的处理,使用图片占位符。
已知问题
1、“直接复制本地文件夹内的图片,在mac下ok,在windows下失效”,找了下原来windows下复制出来的是bolb流(后端服务器接受失败),而mac下是file流。进一步探索发现在windows文件夹系统中,复制文本类的东西,是在剪切板中,可以获得之;但是,复制的图片文件,不论是右键复制,还是Ctrl + C复制都不行。目前,windows复制图片可以选择富文本的插入图片功能实现。(欢迎留言讨论)
2、粘贴后页面滚到底部,解决方案如下:
less文件里添加如下样式。
.ql-clipboard {
position: fixed;
// display: none;
left: 50%;
top: 50%;
}
欢迎多多留言讨论,有好的方案可以留下宝贵意见。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!