原生JS编写简单的编辑器

使用vue编写的,没有任何依赖,可改写其它形式
轻量级编辑器只是在document.execCommand()方法做了包装,不兼容的浏览器器生成的标签是一致的,所以的富文本的选择要根据项目决定

实现了一点功能,写了个dome,加深对富文本原理的理解

在这里插入图片描述
template

<div class="page-editor">
    <div class="editor-wrapper">
        <div class="toolbar">
            <ul @click="menuHandle">
                <li v-for="(item,index) in menuList" :key="index" :data-index="index">
                    <a href="javascript:;" :class="[{'ban-operate': item.isActive}]">
                        <i :class="['iconfont',item.icon]"></i>
                    </a>
                </li>
            </ul>
        </div>
        <div class="body" contenteditable="true" ref="editor" v-focus="true"
             @keyup.enter="enterHandle"
             @keyup.delete="deleteHandle"
        >
        </div>
    </div>
</div>

js

 template: tpl,
    data() {
        return {
            newNode:null,
            lastRange:null,
            selection:window.getSelection(),
            isIE:window.navigator.userAgent.indexOf("MSIE")>=1,
            menuList:[
                {
                    icon:"icon-zitibiaoti",
                    detail:"标题",
                    isActive:false
                },
                {
                    icon:"icon-jiacu",
                    detail:"加粗",
                    isActive:false
                },
                {
                    icon:"icon-xieti",
                    detail:"倾斜",
                    isActive:false
                },
                {
                    icon:"icon-youxuliebiao",
                    detail:"有序列表",
                    isActive:false
                }

            ]

        }
    },
    created(){

    },
    mounted(){
        this.initEditor();
    },
    methods:{
        initEditor(){
            this.$refs.editor.innerHTML = "<p><br/></p>"
        },
        enterHandle(){
            let range = this.getRange();
            let el = this.getNode(range.commonAncestorContainer);
            if(range.collapsed) {
                if (el.nodeName === "DIV") {
                    let p = document.createElement("p");
                    p.innerHTML="<br>";
                    el.parentNode.replaceChild(p,el);
                };
            }
        },
        deleteHandle(){
            const that = this;
            if(!that.$refs.editor.innerHTML){
                that.$refs.editor.innerHTML = "<p><br/></p>"
            }
        },
        menuHandle(el){
            let target = this.getTarget(el,"LI"),
                index = this.dataSet(target,"index");
            switch (index) {
                case 0:
                    this.menuList[index].isActive= !this.menuList[index].isActive;
                    this.titleBtn(this.menuList[index].isActive);
                    break;
                case 1:
                    this.menuList[index].isActive= !this.menuList[index].isActive;
                    this.blodBtn(this.menuList[index].isActive);
                    break;
                case 2:
                    this.menuList[index].isActive= !this.menuList[index].isActive;
                    this.italicBtn(this.menuList[index].isActive);
                    break;
                case 3:
                    this.menuList[index].isActive= !this.menuList[index].isActive;
                    this.olListBtn(this.menuList[index].isActive);
                    break;

            }

        },
        titleBtn(isFouce) {
            console.log(isFouce);
            if(isFouce){
                let insertHtml = "<span style='font-size:28px;'></span>";
                this.addHtml(insertHtml);
            }else{
                this.clearForm();
            }

        },
        blodBtn(isFouce){
            if(isFouce) {

                let insertHtml = "<span style='font-weight:600;'></span>";
                this.addHtml(insertHtml);
            }else{
                this.clearForm();
            }
        },
        italicBtn(isFouce){
            if(isFouce) {

                let insertHtml = "<i></i>";
                this.addHtml(insertHtml);
            }else{
                this.clearForm();
            }
        },
        olListBtn(isFouce){
            if(isFouce) {

                let range = this.getRange();
                document.execCommand("insertOrderedList");
                if (range.collapsed) {
                    let el = range.commonAncestorContainer;
                    while (el.nodeName !== "P") {
                        el = el.parentNode;
                    }
                    ;
                    let replaceNode = el.firstChild.cloneNode(true);
                    el.parentNode.replaceChild(replaceNode, el);

                }
                this.clearRange(range);
            }else{
                this.clearForm();
            }
        },
        clearForm(){
            let range = this.getRange();
            let pNode = range.endContainer.parentNode.parentNode;
            if(pNode){
                let str = pNode.innerHTML.replace(/\u200B/g,'');
                range.selectNode(pNode);      //创建范围的内容
                range.extractContents();
                let fNode = document.createDocumentFragment();

                let newNode = document.createElement(pNode.nodeName);
                if(pNode.attributes['style']){
                    let styles = pNode.attributes['style'].value;

                   newNode.style= styles;

                }


                fNode.appendChild(newNode);
                if(str.indexOf("<br>")>-1){
                    str = str.replace(/\<br>/g,'');
                }
                newNode.innerHTML = str+'\u200B <br>';
                range.insertNode(fNode);

            };

            range.setEndAfter(range.endContainer.parentNode);
            range.collapse(true);
        },
        getHtml(){
            return this.$refs.editor.innerHTML;
        },
        setHtml(val){
            this.$refs.editor.innerHTML = val;
        },
        clearHtml(){
            this.$refs.editor.innerHTML="<p><br></p>";
        },
        getRange(){

            if(this.selection.rangeCount>0){
                return this.selection.getRangeAt(0);
            };
        },
        addHtml(html){
            let range = this.getRange();
            let fragment = range.createContextualFragment(html);
            let node = fragment.firstChild;

            range.insertNode(fragment);
            if(!node.firstChild){
                let text = document.createTextNode("\u200B");
                node.appendChild(text)
            };
            range.setStart(node.firstChild,1);
            range.setEnd(node.firstChild,1);
            range.collapse(true);
            this.clearRange(range);
        },
        clearRange(range){
            this.lastRange = range.cloneRange();

            range.detach();
            range = null;
            this.selection.removeAllRanges();

            this.selection.addRange(this.lastRange);
        },


        dataSet(el,name){
            let index;
            if(el){

                if (el.dataset) {
                    index = parseInt(el.dataset[name]);
                } else {
                    index = parseInt(el.getAttribute('data-'+name));
                }
                return parseInt(index);
            }

        },
        getTarget(el,targetName) {
            let target = el.target;
            while(target.nodeName!==targetName){
                target = target.parentNode;
            };
            el.stopPropagation();
            return target;
        },
        getNode(el){
            while(el.nodeType!==1){
                el = el.parentNode;
            };
            return el;
        }





    },
    directives: {
        focus: {
            inserted: function (el,binding) {
                if(binding.value){
                    el.focus();
                }
            }
        }
    }

less

.page-editor{
  position: relative;
  border: 1px solid #c9d8db;
  margin: 15px;
  .editor-wrapper{
    position: relative;
    background: #fff;
  }
  .toolbar{
    border-bottom: 1px solid #eee;
    background: #fff;
    ul li{
      position: relative;
      display: inline-block;
      font-size: 0;
    }
    ul li a{
      display: inline-block;
      width: 46px;
      height: 40px;
      outline: 0;
      color: #333;
      font-size: 14px;
      line-height: 40px;
      vertical-align: middle;
      text-align: center;
      text-decoration: none;
    }
    ul li a i{
      font-size: 18px;
    }
  }
  .body{
    font-size: 16px;
    font-family: arial,sans-serif;
    line-height: 1.6;
    color: #333;
    outline: 0;
    word-wrap: break-word;
    padding: 22px 15px 40px;
    min-height: 300px;
    cursor: text;
    position: relative;
    z-index: 1;
    background: 0 0;
  }
  .body{
    ol{
      margin-left: 15px;
    }
  }
  .ban-operate{
     background-color: #909399;
  }



}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值