小程序H5页面使用富文本quill

let speakEditor = {};
if (process.env.TARO_ENV === 'h5') {
    speakEditor = require('./quill-editor');
}
const SpeakEditor = speakEditor.default || <View />;

// 外层back使用相对定位,这样SpeakEditor使用绝对定位时位置就没问题了
<View className={style.back}>
    <SpeakEditor onInput={handleInputChange} value={''} />
</View>


// SpeakEditor组件index.jsx
import {createRef, useCallback, useEffect, useImperativeHandle, useRef} from 'react';
import {Events} from '@tarojs/taro';
import {View} from '@tarojs/components';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import 'tippy.js/dist/tippy.css';
import style from './style.less';

export const events = new Events();

const oneSelfRef = createRef();
export default function SpeakEditor(props) {
    const {value, onInput} = props;
    const reactQuillRef = useRef();
    const quillRef = useRef();

    useImperativeHandle(oneSelfRef, () => ({
        getText() {
            return quillRef.current.getText();
        },
        getContents() {
            return quillRef.current.getContents();
        },
        loadContents(contents) {
            quillRef.current.setContents(contents);
        },
        editorEvent(eventName, handler) {
            quillRef.current.root.addEventListener(eventName, handler);
        },
        editorOffEvent(eventName, handler) {
            quillRef.current.root.removeEventListener(eventName, handler);
        },
        call(method, ...args) {
            return quillRef.current[method](...args);
        },
        focusOnSelection() {
            // 滚动到光标处
            const editor = quillRef.current; // quill编辑器实例
            const selection = editor.getSelection(); // 获取选中范围
            if (selection) {
                const [line] = editor.getLines(selection.index, 1);
                if (line) {
                    editor.scrollIntoView(line);
                }
            }
        }
    }));

    useEffect(() => {
        quillRef.current = reactQuillRef.current.getEditor();
    }, []);

    const handleChange = useCallback(
        (_value, delta, source, editor) => {
            // console.log('Change: ', {_value, delta, source, editor}, editor.getContents());
            onInput({detail: {value: _value}});
        },
        [onInput]
    );

    return (
        <View className={style.quill_editor}>
            <ReactQuill theme="snow" value={value} onChange={handleChange} ref={reactQuillRef} />
        </View>
    );
}

export function event(eventName, handler) {
    if (oneSelfRef.current) {
        oneSelfRef.current.call('on', eventName, handler);
    } else if (event.counter < 20) { 
        event.counter++; 
        setTimeout(()=> {
            event(eventName, handler);
        }, 200);
    }
}
event.counter = 0;

export function offEvent(eventName, handler) {
    oneSelfRef.current?.call('off', eventName, handler);
}

export function editorEvent(eventName, handler) {
    if (oneSelfRef.current) {
        oneSelfRef.current.editorEvent(eventName, handler);
    } else if (editorEvent.counter < 20) { editorEvent.counter++; setTimeout(()=> {
        oneSelfRef.current.editorEvent(eventName, handler);
        }, 200);
    }
}
editorEvent.counter = 0;

export function editorOffEvent(eventName, handler) {
    oneSelfRef.current.editorOffEvent(eventName, handler);
}

export function on(eventName, handler) {
    events.on(eventName, handler);
}

export function off(eventName, handler) {
    if (!eventName || !handler) {
        events.off();
    } else {
        events.off(eventName, handler);
    }
}

export function getText() {
    return oneSelfRef.current?.getText();
}

export function getContents() {
    return oneSelfRef.current?.getContents();
}

export function loadContents(contents) {
    return oneSelfRef.current?.loadContents(contents);
}

export function doBlur() {
    console.log('doBlur');
    return oneSelfRef.current?.call('blur');
}

export function doFocus() {
    console.log('doFocus');
    return oneSelfRef.current?.call('focus');
}

export function focusOnSelection() {
    oneSelfRef.current?.focusOnSelection();
}

// SpeakEditor组件style.less
.quill_editor {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;

    :global {
        .quill {
            width: 100%;
            height: 100%;
            user-select: text;
        }

        .ql-toolbar {
            display: none;
        }

        .ql-container {
            font-size: 28px;
            color: white;
            border: none;
        }

        .ql-editor {
            padding: 0;
            line-height: 38px;
            letter-spacing: 4px;
        }

        .pause_embed {
            display: inline-block;
            margin: 6px 0;
            margin-right: 3px;
            font-size: 28px !important;
            color: #468df7;
            pointer-events: auto;
        }

        .reading_method {
            display: inline-block;
            padding: 0;
            padding-left: 4px;
            margin: 6px 0;
            margin-right: 3px;
            font-size: inherit !important;
            font-weight: inherit !important;
            pointer-events: none;
            // background: rgb(66, 44, 54);
            background: rgba(239, 161, 56, 0.32);
            border-radius: 4px;

            &::after {
                display: inline-block;
                height: 100%;
                padding: 3px 4px;
                margin-left: 4px;
                font-size: 22px;
                color: #efa138;
                pointer-events: auto;
                background: rgba(239, 161, 56, 0.16);
                content: attr(data-str);
            }
        }

        .shift_speed {
            display: inline-block;
            margin: 6px 0;
            margin-right: 3px;
            font-size: inherit !important;
            font-weight: inherit !important;
            pointer-events: none;

            &::before {
                color: #468df7;
                content: '【';
            }

            &::after {
                color: #468df7;
                pointer-events: auto;
                content: '】' attr(data-value) 'x';
            }
        }

        .chars_check {
            display: inline-block;
            padding: 0;
            padding-left: 4px;
            margin: 6px 0;
            margin-right: 3px;
            font-size: inherit !important;
            font-weight: inherit !important;
            // pointer-events: none;
            background: rgba(82, 196, 26, 0.16);
            border-radius: 4px;

            &::after {
                display: inline-block;
                padding: 3px 4px;
                margin-bottom: 5px;
                margin-left: 4px;
                font-size: 22px;
                vertical-align: middle;
                // pointer-events: auto;
                content: attr(data-str);
            }

            &[data-str=''] {
                &::after {
                    width: 24px;
                    height: 24px;
                    margin-right: 7px;
                    margin-left: 3px;
                    background: center no-repeat;
                    background-size: contain;
                    background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE5LjYxMDUgMTEuODcwMkMxOS42MTA1IDExLjM4OTYgMjAuMDAwMSAxMSAyMC40ODA2IDExSDIzLjk2MTNDMjQuNDQxOCAxMSAyNC44MzE0IDExLjM4OTYgMjQuODMxNCAxMS44NzAyVjExLjg3MDJDMjQuODMxNCAxMi4zNTA3IDI0LjQ0MTggMTIuNzQwMyAyMy45NjEzIDEyLjc0MDNIMjAuNDgwNkMyMC4wMDAxIDEyLjc0MDMgMTkuNjEwNSAxMi4zNTA3IDE5LjYxMDUgMTEuODcwMlYxMS44NzAyWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yMS41NTU4IDI0LjI5NzJDMjEuNjk1NSAyNC4zMDk5IDIxLjg0MDggMjQuMzE2MiAyMS45OTE1IDI0LjMxNjJDMjIuNzc3OCAyNC4zMTYyIDIzLjQ2MTYgMjQuMTYyNCAyNC4wNDI4IDIzLjg4ODlDMjQuNTU1NiAyMy42MzI1IDI0Ljk4MjkgMjMuMjczNSAyNS4zMjQ4IDIyLjgyOTFWMjQuMDc2OUgyN1YxOC40MzU5QzI3IDE3LjM1OSAyNi43MjY1IDE2LjUzODUgMjYuMTk2NiAxNS45NzQ0QzI1LjU4MTIgMTUuMzI0OCAyNC42MjQgMTUgMjMuMzI0OCAxNUMyMi4yMzA4IDE1IDIxLjM0MTkgMTUuMTg4IDIwLjY5MjMgMTUuNTk4M0MyMC4yMTMzIDE1Ljg4MTMgMTkuODQ1MyAxNi4yNzUzIDE5LjU5MjUgMTYuNzcxNEwxOS44NTI0IDE3Ljc2NzZMMjEuMDY4NCAxNy44NzE4QzIxLjE3MSAxNy4zNzYxIDIxLjQyNzQgMTcuMDE3MSAyMS44Mzc2IDE2Ljc3NzhDMjIuMTc5NSAxNi41NzI2IDIyLjY0MSAxNi40NzAxIDIzLjIwNTIgMTYuNDcwMUMyNC41Mzg1IDE2LjQ3MDEgMjUuMjA1MiAxNy4wODU1IDI1LjIwNTIgMTguMzE2MlYxOC42NzUyTDIzLjIyMjIgMTguNzI2NUMyMS45ODk2IDE4Ljc1ODkgMjEuMDAzMSAxOC45OTE0IDIwLjI5MjEgMTkuNDUzMkwyMC44NjU2IDIxLjY1MTdDMjAuOTE1MSAyMC42MjI1IDIxLjczNTMgMjAuMDc1OSAyMy4zNDE5IDIwLjA0MjdMMjUuMjA1MiAxOS45OTE1VjIwLjUwNDNDMjUuMjA1MiAyMS4xODggMjQuOTE0NiAyMS43NjkyIDI0LjM2NzUgMjIuMjMwOEMyMy44MjA1IDIyLjY5MjMgMjMuMTcxIDIyLjkzMTYgMjIuNDAxNyAyMi45MzE2QzIxLjk0MDIgMjIuOTMxNiAyMS41NjQxIDIyLjgxMiAyMS4yOTA2IDIyLjU4OTdDMjEuMTkxNCAyMi41MTM5IDIxLjExMDIgMjIuNDMyMSAyMS4wNDYxIDIyLjM0MzZMMjEuNTU1OCAyNC4yOTcyWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNS4yMTc0IDI2TDE2IDI5TDI2IDI5QzI4LjIwOTIgMjkgMzAgMjcuMjA5MSAzMCAyNUwzMCAxMEMzMCA3Ljc5MDg2IDI4LjIwOTIgNiAyNiA2SDE2Ljc4MjZMMTcuMzA0NCA4TDI2IDhDMjcuMTA0NiA4IDI4IDguODk1NDMgMjggMTBMMjggMjVDMjggMjYuMTA0NiAyNy4xMDQ2IDI3IDI2IDI3TDE3LjU0NTIgMjdMMTcuMjg0MyAyNkgxNS4yMTc0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNC40NTQ4IDVINkM0Ljg5NTQzIDUgNCA1Ljg5NTQzIDQgN1YyMkM0IDIzLjEwNDYgNC44OTU0MyAyNCA2IDI0SDE5LjQxMTNMMTQuNDU0OCA1Wk0xNiAzSDZDMy43OTA4NiAzIDIgNC43OTA4NiAyIDdWMjJDMiAyNC4yMDkxIDMuNzkwODYgMjYgNiAyNkgyMkwxNiAzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMS4wMTYzIDEwLjA5ODdDMTAuNzMyOCAxMC4xNjUyIDEwLjQyMjUgMTAuMDg3OSAxMC4yMDE1IDkuODY2ODlMOC4zNTU1OSA4LjAyMTAxQzguMDE1NzcgNy42ODExOSA4LjAxNTc3IDcuMTMwMjQgOC4zNTU1OSA2Ljc5MDQyQzguNjk1NDEgNi40NTA2MSA5LjI0NjM2IDYuNDUwNjEgOS41ODYxOCA2Ljc5MDQyTDEwLjgxNjggOC4wMjEwMkwxMi4wNDc0IDYuNzkwNDFDMTIuMzg3MiA2LjQ1MDYgMTIuOTM4MiA2LjQ1MDYgMTMuMjc4IDYuNzkwNDFDMTMuNjE3OCA3LjEzMDIzIDEzLjYxNzggNy42ODExOCAxMy4yNzggOC4wMjFMMTEuNDMyMSA5Ljg2Njg4QzExLjMxMzMgOS45ODU2OSAxMS4xNjg3IDEwLjA2MyAxMS4wMTYzIDEwLjA5ODdaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTAuODc2OCAxMS45NzE5QzEyLjE3NTkgMTEuOTcxOSAxMy4xMzMyIDEyLjI5NjYgMTMuNzQ4NiAxMi45NDYyQzE0LjI3ODUgMTMuNTEwMyAxNC41NTIgMTQuMzMwOCAxNC41NTIgMTUuNDA3OFYyMS4wNDg4SDEyLjg3NjhWMTkuODAwOUMxMi41MzQ5IDIwLjI0NTQgMTIuMTA3NiAyMC42MDQzIDExLjU5NDcgMjAuODYwOEMxMS4wMTM1IDIxLjEzNDMgMTAuMzI5OCAyMS4yODgxIDkuNTQzNDUgMjEuMjg4MUM4LjYyMDM4IDIxLjI4ODEgNy45MDI0MyAyMS4wNDg4IDcuMzg5NjEgMjAuNTg3MkM2LjgyNTUxIDIwLjEyNTcgNi41NTIgMTkuNTI3NCA2LjU1MiAxOC43OTI0QzYuNTUyIDE3LjgwMDkgNi45NDUxNiAxNy4wMzE3IDcuNzMxNDkgMTYuNTAxOEM4LjQ0OTQ0IDE1Ljk4OSA5LjQ3NTA4IDE1LjczMjUgMTAuNzc0MiAxNS42OTg0TDEyLjc1NzEgMTUuNjQ3MVYxNS4yODgxQzEyLjc1NzEgMTQuMDU3MyAxMi4wOTA1IDEzLjQ0MTkgMTAuNzU3MSAxMy40NDE5QzEwLjE5MyAxMy40NDE5IDkuNzMxNDkgMTMuNTQ0NSA5LjM4OTYxIDEzLjc0OTZDOC45NzkzNSAxMy45ODkgOC43MjI5NCAxNC4zNDc5IDguNjIwMzggMTQuODQzN0w2LjgyNTUxIDE0LjY4OThDNy4wMTM1NCAxMy43MzI1IDcuNDkyMTcgMTMuMDE0NiA4LjI0NDMxIDEyLjU3MDJDOC44OTM4OCAxMi4xNTk5IDkuNzgyNzcgMTEuOTcxOSAxMC44NzY4IDExLjk3MTlaTTEyLjc1NzEgMTYuOTYzM0wxMC44OTM5IDE3LjAxNDZDOS4yMzU3NiAxNy4wNDg4IDguNDE1MjUgMTcuNjMgOC40MTUyNSAxOC43MjRDOC40MTUyNSAxOS4wNjU5IDguNTUyIDE5LjMzOTQgOC44NDI2IDE5LjU2MTZDOS4xMTYxIDE5Ljc4MzggOS40OTIxNyAxOS45MDM1IDkuOTUzNzEgMTkuOTAzNUMxMC43MjI5IDE5LjkwMzUgMTEuMzcyNSAxOS42NjQyIDExLjkxOTUgMTkuMjAyNkMxMi40NjY1IDE4Ljc0MTEgMTIuNzU3MSAxOC4xNTk5IDEyLjc1NzEgMTcuNDc2MVYxNi45NjMzWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==);
                }
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值