使用execCommand将文字样式设置成css样式的办法

1,272 阅读4分钟

使用execCommand将文字样式设置成css样式的办法

前言

昨天在解决富文本编辑器umeditor中增加更多字号的需求,发现一个非常有意思的 API execCommand 。我们可以通过这个 API 操控富文本中的内容。关于此**execCommand 详解**,基于以上知识的介绍,来看看如何在umeditor组件基础上增加更多字号

问题描述

看完umeditor文档我想你应该知道,给编辑器增加字号再简单不过了,就是在配置文件中umeditor.config.js增加一行代码就完事儿了,像这样:

fontsize: [ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 60, 72, 96]

image-20210608143549125

然而我加完发现,有如下的bug:

输入时候选择字号输入字号是正确展示,不过我想把输入完的某些字改字号,这时候不正确显示设置的字号,如下:

于是开始看源码,定位到比较关键的代码如下:

源码中有默认的字号:

    ///import core
    ///import plugins\removeformat.js
    ///commands 字体颜色,背景色,字号,字体,下划线,删除线
    ///commandsName  ForeColor,BackColor,FontSize,FontFamily,Underline,StrikeThrough
    ///commandsTitle  字体颜色,背景色,字号,字体,下划线,删除线
    /**
     * @description 字体
     * @name UM.execCommand
     * @param {String}     cmdName    执行的功能名称
     * @param {String}    value             传入的值
     */
    UM.plugins['font'] = function () {
        var me = this,
            fonts = {
                'forecolor': 'forecolor',
                'backcolor': 'backcolor',
                'fontsize': 'fontsize',
                'fontfamily': 'fontname'
            },
            cmdNameToStyle = {
                'forecolor': 'color',
                'backcolor': 'background-color',
                'fontsize': 'font-size',
                'fontfamily': 'font-family'
            },
            cmdNameToAttr = {
                'forecolor': 'color',
                'fontsize': 'size',
                'fontfamily': 'face'
            };
        me.setOpt({
            'fontfamily': [{
                    name: 'songti',
                    val: '宋体,SimSun'
                },
                {
                    name: 'yahei',
                    val: '微软雅黑,Microsoft YaHei'
                },
                {
                    name: 'kaiti',
                    val: '楷体,楷体_GB2312, SimKai'
                },
                {
                    name: 'heiti',
                    val: '黑体, SimHei'
                },
                {
                    name: 'lishu',
                    val: '隶书, SimLi'
                },
                {
                    name: 'andaleMono',
                    val: 'andale mono'
                },
                {
                    name: 'arial',
                    val: 'arial, helvetica,sans-serif'
                },
                {
                    name: 'arialBlack',
                    val: 'arial black,avant garde'
                },
                {
                    name: 'comicSansMs',
                    val: 'comic sans ms'
                },
                {
                    name: 'impact',
                    val: 'impact,chicago'
                },
                {
                    name: 'timesNewRoman',
                    val: 'times new roman'
                },
                {
                    name: 'sans-serif',
                    val: 'sans-serif'
                }
            ],
            // 'fontsize': [10, 12, 16, 18, 24, 32, 48]
            'fontsize': [ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 60, 72, 96]
        });

        me.addOutputRule(function (root) {
            utils.each(root.getNodesByTagName('font'), function (node) {
                if (node.tagName == 'font') {
                    var cssStyle = [];
                    for (var p in node.attrs) {
                        switch (p) {
                            case 'size':
                                console.log(node.attrs)
                                var val = node.attrs[p];
                                console.log('dddd333', val);
                                $.each(
                                    // {
                                    // '10': '1',
                                    // '12': '2',
                                    // '16': '3',
                                    // '18': '4',
                                    // '24': '5',
                                    // '32': '6',
                                    // '48': '7'
                                    // },
                                    {
                                        '8': '1',
                                        '9': '2',
                                        '10': '3',
                                        '11': '4',
                                        '12': '5',
                                        '13': '6',
                                        '14': '7',
                                        '15': '8',
                                        '16': '9',
                                        '17': '10',
                                        '18': '11',
                                        '19': '12',
                                        '20': '13',
                                        '21': '14',
                                        '22': '15',
                                        '23': '16',
                                        '24': '17',
                                        '25': '18',
                                        '26': '19',
                                        '27': '20',
                                        '28': '21',
                                        '29': '22',
                                        '30': '23',
                                        '31': '24',
                                        '32': '25',
                                        '33': '26',
                                        '34': '27',
                                        '35': '28',
                                        '36': '29',
                                        '37': '30',
                                        '38': '31',
                                        '39': '32',
                                        '40': '33',
                                        '41': '34',
                                        '42': '35',
                                        '43': '36',
                                        '44': '37',
                                        '45': '38',
                                        '46': '39',
                                        '47': '40',
                                        '48': '41',
                                        '60': '42',
                                        '72': '43',
                                        '96': '44',                                       
                                    }, function (k, v) {
                                        console.log('dddddd44',k, v)
                                        if (v == val) {
                                            val = k;
                                            return false;
                                        }
                                    });
                                console.log('dddddddddddddddddddddfont-size:',val)
                                cssStyle.push('font-size:' + val + 'px');
                                break;
                            case 'color':
                                cssStyle.push('color:' + node.attrs[p]);
                                break;
                            case 'face':
                                cssStyle.push('font-family:' + node.attrs[p]);
                                break;
                            case 'style':
                                cssStyle.push(node.attrs[p]);
                        }
                    }
                    node.attrs = {
                        'style': cssStyle.join(';')
                    };
                }
                node.tagName = 'span';
                if (node.parentNode.tagName == 'span' && node.parentNode.children.length == 1) {
                    $.each(node.attrs, function (k, v) {

                        node.parentNode.attrs[k] = k == 'style' ? node.parentNode.attrs[k] + v : v;
                    })
                    node.parentNode.removeChild(node, true);
                }
            });
        });
        for (var p in fonts) {
            (function (cmd) {
                me.commands[cmd] = {
                    execCommand: function (cmdName, value) {
                        console.log('d6',value)
                        if (value == 'transparent') {
                            return;
                        }
                        var rng = this.selection.getRange();
                        if (rng.collapsed) {
                            var span = $('<span></span>').css(cmdNameToStyle[cmdName], value)[0];
                            rng.insertNode(span).setStart(span, 0).setCursor();
                        } else {
                            if (cmdName == 'fontsize') {
                                // value = {
                                //     '10': '1',
                                //     '12': '2',
                                //     '16': '3',
                                //     '18': '4',
                                //     '24': '5',
                                //     '32': '6',
                                //     '48': '7'
                                // }
                                value = {
                                    '8': '1',
                                    '9': '2',
                                    '10': '3',
                                    '11': '4',
                                    '12': '5',
                                    '13': '6',
                                    '14': '7',
                                    '15': '8',
                                    '16': '9',
                                    '17': '10',
                                    '18': '11',
                                    '19': '12',
                                    '20': '13',
                                    '21': '14',
                                    '22': '15',
                                    '23': '16',
                                    '24': '17',
                                    '25': '18',
                                    '26': '19',
                                    '27': '20',
                                    '28': '21',
                                    '29': '22',
                                    '30': '23',
                                    '31': '24',
                                    '32': '25',
                                    '33': '26',
                                    '34': '27',
                                    '35': '28',
                                    '36': '29',
                                    '37': '30',
                                    '38': '31',
                                    '39': '32',
                                    '40': '33',
                                    '41': '34',
                                    '42': '35',
                                    '43': '36',
                                    '44': '37',
                                    '45': '38',
                                    '46': '39',
                                    '47': '40',
                                    '48': '41',
                                    '60': '42',
                                    '72': '43',
                                    '96': '44'
                                }
                                [(value + "").replace(/px/, '')]
                                console.log('d7:',value)
                            }
                            // TODO 目前execCommand只支持1-7  https://www.yuque.com/trisolar/odtpgb/tv6wyd
                            this.document.execCommand(fonts[cmdName], false, value);
                            console.log('d8:', fonts[cmdName])
                            console.log('d9:', value)
                            console.log('browser.gecko:', browser.gecko)
                            console.log('!browser.ie:',browser.gecko)
                            if (browser.gecko) {
                                $.each(this.$body.find('a'), function (i, a) {
                                    var parent = a.parentNode;
                                    if (parent.lastChild === parent.firstChild && /FONT|SPAN/.test(parent.tagName)) {
                                        var cloneNode = parent.cloneNode(false);
                                        cloneNode.innerHTML = a.innerHTML;
                                        $(a).html('').append(cloneNode).insertBefore(parent);

                                        $(parent).remove();
                                    }
                                });
                            }
                            if (!browser.ie) {
                                var nativeRange = this.selection.getNative().getRangeAt(0);
                                var common = nativeRange.commonAncestorContainer;
                                var rng = this.selection.getRange(),
                                    bk = rng.createBookmark(true);

                                $(common).find('a').each(function (i, n) {
                                    var parent = n.parentNode;
                                    if (parent.nodeName == 'FONT') {
                                        var font = parent.cloneNode(false);
                                        font.innerHTML = n.innerHTML;
                                        $(n).html('').append(font);
                                    }
                                });
                                rng.moveToBookmark(bk).select()
                            }
                            return true
                        }

                    },
                    queryCommandValue: function (cmdName) {
                        console.log('d1',cmdName)
                        var start = me.selection.getStart();
                        console.log($(start))
                        var val = $(start).css(cmdNameToStyle[cmdName]);
                        console.log('d2',val)
                        if (val === undefined) {
                            val = $(start).attr(cmdNameToAttr[cmdName])
                        }
                        console.log('d3:', cmdName, val)
                        console.log('d4:',val ? utils.fixColor(cmdName, val).replace(/px/, '') : '')
                        return val ? utils.fixColor(cmdName, val).replace(/px/, '') : '';
                    },
                    queryCommandState: function (cmdName) {
                        console.log('d5',this.queryCommandValue(cmdName))
                        return this.queryCommandValue(cmdName)
                    }
                };
            })(p);
        }
    };

嗯,第一感觉就是把这俩地方改了,改了之后还是不生效,继续断点排查,发现

this.document.execCommand(fonts[cmdName], false, value);

这个value无论传几,但是浏览器中展示的字号最大是7,如下:

image-20210608145557760

现在已经找到原因了就是这个execCommand导致的!!!

关于execCommand的fontSize

设置选中区域或光标位置的字体大小。实际测试浏览器是通过 font 标签的 size 属性设置字体大小,只接受 1 ~ 7的数值。是一个相对值,目前看实用性不大。

示例:document.execCommand('fontName', false, fontSize)

我们在使用contenteditable属性做富文本编辑器时经常会用到document.execCommand方法来处理文字的样式。但当我们要设置比如font-size,会发现这个命令只支持(1-7)这几种字体大小值,而不是我们想要的类似css样式的值详细文档。有此类问题的样式还包括行高、颜色等到。但实际上我们的需求可能是将其设置成css样式,那就可以使用下面的方法来实现。

解决方案

使用execCommand将文字样式设置成css样式的办法:

以设置字体大小为例:

// 为处理execCommand问题,采用如下方法
this.document.execCommand('styleWithCSS', null, true);
// 先将文字大小设置成1-7号中的任何一个大小
this.document.execCommand('fontSize', false, 1);
// 这时候浏览器会默认将文字包裹一层span,然后css设置在span上,然后我们再去寻找这个span,将其css修改成我们实际想要的字体大小
// #editorid → 正在编辑的元素的父容器
const $parent = document.querySelector('#editorid');
let $spans = $parent.querySelectorAll('span');
$spans.forEach((Ispan) => {
    const fs = Ispan.style.fontSize;
    if ('x-small' == fs) {
      	// 设置的文字大小
        const fts = `font-size:${value}px!important`;
        Ispan.setAttribute('style', fts);
        //这种方式设置fontSize不生效
        // Ispan.style.fontSize = `${value}px!important`;
    }
});

附上修改后umeditor.customize.js上下文代码:

execCommand: function (cmdName, value) {
    if (value == 'transparent') {
        return;
    }
    var rng = this.selection.getRange();
    if (rng.collapsed) {
        var span = $('<span></span>').css(cmdNameToStyle[cmdName], value)[0];
        rng.insertNode(span).setStart(span, 0).setCursor();
    } else {
        if (cmdName == 'fontsize') {
            // 为处理execCommand问题,采用如下方法
            this.document.execCommand('styleWithCSS', null, true);
            // 先将文字大小设置成1-7号中的任何一个大小
            this.document.execCommand('fontSize', false, 1);
            // 这时候浏览器会默认将文字包裹一层span,然后css设置在span上,然后我们再去寻找这个span,将其css修改成我们实际想要的字体大小
            const $parent = document.querySelector('#editorid');
            let $spans = $parent.querySelectorAll('span');
            $spans.forEach((Ispan) => {
                const fs = Ispan.style.fontSize;
                if ('x-small' == fs) {
                    Ispan.setAttribute('style', `font-size:${value}px!important`);
                    //这种方式设置fontSize不生效
                    // Ispan.style.fontSize = `${value}px!important`;
                }
            });
        } else {
            // 1.目前execCommand中fontSize只支持1-7的数值  https://www.yuque.com/trisolar/odtpgb/tv6wyd
            this.document.execCommand(fonts[cmdName], false, value);
        }
        if (browser.gecko) {
            $.each(this.$body.find('a'), function (i, a) {
                var parent = a.parentNode;
                if (parent.lastChild === parent.firstChild && /FONT|SPAN/.test(parent.tagName)) {
                    var cloneNode = parent.cloneNode(false);
                    cloneNode.innerHTML = a.innerHTML;
                    $(a).html('').append(cloneNode).insertBefore(parent);

                    $(parent).remove();
                }
            });
        }
        if (!browser.ie) {
            var nativeRange = this.selection.getNative().getRangeAt(0);
            var common = nativeRange.commonAncestorContainer;
            var rng = this.selection.getRange(),
                bk = rng.createBookmark(true);

            $(common).find('a').each(function (i, n) {
                var parent = n.parentNode;
                if (parent.nodeName == 'FONT') {
                    var font = parent.cloneNode(false);
                    font.innerHTML = n.innerHTML;
                    $(n).html('').append(font);
                }
            });
            rng.moveToBookmark(bk).select()
        }
        return true
    }

},

看下修改后:

image-20210608152100577

其它样式调整也可以用该方法找到对应的span后修改style值实现,只是最后要把font-size='x-small'的样式清空。

更多前端技术文章请关注微信公众号:xiaoyuanlianer666 掘金账号:小圆脸儿