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

最近项目中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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容