美文网首页
React H5 使用div自定义简单富文本编辑器

React H5 使用div自定义简单富文本编辑器

作者: Bee大前端 | 来源:发表于2019-08-24 00:28 被阅读0次

最近项目中h5端要实现图文上传,而且还要支持用户用户输入的格式,例如换行啥的,那么使用输入控件保存输入内容,图片上传控件就不合适了,因为很难知道用户的输入样式。

如果使用一些现有的富文本编辑器,貌似又不是很划算,所以综合考虑使用div来自己实现一个就是比较理想的方案了。

先来考虑一下,如果使用div来实现简单的富文本编辑器,需要解决哪些问题?

首先,div默认是不可编辑的,需要设置它可以编辑,这个很简单只需要使用

   contentEditable="true"

其次,我们需要在div指定的位置插入图片,指定位置就是光标所在的位置,那么我们插入图片的时候,需要知道光标的位置,网上查了下应该是需要getSelection API W3Cschool上面直接有简单的用法,感兴趣的童鞋可以去看看。

获取当前光标选中位置

let selection = window.getSelection();
let range = selection.getRangeAt(0);

那么知道了光标位置,怎么插入图片?

 let img = document.createElement("img");
   img.src = json.url;
   //最大宽度为手机宽度减去20
   img.style.maxWidth = (width-20) + 'px';
   range.insertNode(img);//选中位置插入图片   

这就完了?

当然还没完,实现中遇到了另一个问题,那就是焦点的变化。如果我想插入图片,我就首先需要上传图片,但是当我点击上传图片的按钮的时候,焦点就已经发生了变化,这个时候获取的选中位置就是你点击的位置!

那么该如何解决这个问题呢?

仔细分析,点击的时候,焦点变化的时候,我们需要记录下上次焦点的位置,上次焦点的位置在哪里?在可编辑的div中,焦点变化的时候,就是可编辑div失去焦点的时候!

那么是否可以监听div失焦这个事件呢?答案当然是的!

 onBlur={() => {
   let selection = window.getSelection();
   range = selection.getRangeAt(0);
   }}

给div实现onBlur监听就可以了,是不是很简单?

接下来看一下完整的代码

import React, {Component} from "react";
import "../../assets/css/topic/DivEdit.css";
import "../../assets/common/second-common.css";
​
let range;
let width = document.documentElement.clientWidth;
//头部栏
class DivEdit extends Component {
    constructor(props) {
        super(props);
        let typeArr = [".png", ".jpg", ".jpeg", ".bmp", ".gif"];
        this.state = {
            id:'',
            inputValueHtml: '',
            fileType: typeArr,
            showTips:true
        }
    }
​
​
    //光标定位在可编辑div的开始的位置
    setStartFocus() {
​
        document.querySelector('#my-question-define-edit').focus();
​
    }
​
​
    //光标定位在内容的尾端
    setEndFocus() {
​
​
        let srcObj = document.querySelector('#my-question-define-edit');
        let selection = window.getSelection();
        range = document.createRange();
        range.selectNodeContents(srcObj);
        selection.removeAllRanges();
        selection.addRange(range);
        range.setStart(srcObj, 1);
        range.setEnd(srcObj, 1);
​
    }
​
​
    // 调用input 选择文件
    triggerSelect() {
        return document.getElementById('topic-add-img').click();
    }
​
​
    //处理上传文件
    handleChange(file) {
        // 判断文件类型进行上传
        if (window.typeMatch(this.state.fileType, file.name)) {
            // 上传文件
            this.saveFile(file);
        } else {
            Toast.fail("文件类型不符,请重新选择", 3);
        }
    }
​
    // 上传文件
    saveFile(file) {
     
     //你们自己处理上传的处理 //当成功的时候
    
             let img = document.createElement("img");
             img.src = json.url;
             img.style.maxWidth = (width-20) + 'px';
​
            if(range) {
​
                  range.insertNode(img);
​
           }else{
​
                this.setStartFocus();
                let selection = window.getSelection();
                range = selection.getRangeAt(0);
                range.insertNode(img);
​
              }
   
    }
​
    componentDidMount() {
        let question=this.props.question;
        this.setState({
        inputValueHtml: '<p>' + question + '</p><br/><br/><br/>',showTips:!(question&&question!=='')
        }, 
        () => {
​
            if(question&&question!==''){
​
                this.setEndFocus();
​
            }
        });
​
    }
​
​
    getHtml(){
​
        return this._editDiv.innerHTML;
​
    }
​
​
    render() {
​
​
        return (
​
            <div className={'add-my-question-first-edit'} >
​
                <div id={'my-question-define-edit'}
                     onFocus={() => {
​
                         this.setState({showTips:false});
​
                     }}
                     onBlur={() => {
                         let selection = window.getSelection();
                         range = selection.getRangeAt(0);
                     }}
                     onClick={() => {
​
                         let selection = window.getSelection();
                         range = selection.getRangeAt(0);
​
                     }}
                     ref={(editDiv) => this._editDiv = editDiv}
                     className={'add-my-question-edit'}
                     contentEditable="true"
                     dangerouslySetInnerHTML={{__html: this.state.inputValueHtml}}/>
​
                {
​
                    this.state.showTips?
                        <span style={{
                        position: 'absolute',
                        top:10,
                        left:10,
                        marginTop:'1.333rem',
                        fontSize:17,color:'#E8E8E8'
                        }}>输入您的问题...</span>:null
​
                }
​
                <div className={'add-bottom-menu'}>
​
                    <img style={{width: 20, position: 'absolute', left: 10}}
                         src={require("../../assets/images/topic_topic_show.png")}
                         alt='箭头'/>
​
​
                    <div className={'topic-add-pic'}
                         onClick={() => {
                              //调起上传图片
                             this.triggerSelect();
                             
                         }}>
                        <img style={{width: 20}}
                             src={require("../../assets/images/topic_upload_pic.png")}
                             alt='上传图片'/>
​
                        <span style={{marginLeft: 10, fontSize: 15}}>上传图片</span>
                    </div>
​
​
                </div>
                
                <div style={{visibility: 'hidden'}}>
                    <input
                        type="file"
                        id={'topic-add-img'}
                        style={{display: "none"}}
                        onChange={e => this.handleChange(e.target.files[0])}
                    />
                </div>
​
            </div>
​
        );
    }
}
​
export default DivEdit;​

样式表

.add-my-question-first-edit {
    display: flex;
    flex-direction: column;
    flex: 1;
​
 }
​
.add-my-question-first-edit  .add-my-question-edit{
​
    flex: 1;
    margin-top: 1.333rem;
    /*margin-bottom: 1.333rem;*/
    font-size: 0.453rem;
    background-color: #fff;
    padding: 10px 10px 1.867rem;
​
}
​
.add-my-question-first-edit  .add-bottom-menu{
​
    position: fixed;
    bottom: 0;
    width: 100%;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    height: 1.067rem;
    background-color: #fff;
    border-top: #E8E8E8 solid 1px;
​
​
​
}
​
.add-my-question-first-edit  .add-bottom-menu .topic-add-pic{
​
    display: flex;
    flex-direction: row;
    align-items: center;
    align-self: center;
​
}

更多内容,欢迎同步关注作者二维码!


qrcode_for_gh_f730c342ff6e_344.jpg

相关文章

网友评论

      本文标题:React H5 使用div自定义简单富文本编辑器

      本文链接:https://www.haomeiwen.com/subject/norxectx.html