使用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]
然而我加完发现,有如下的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,如下:
现在已经找到原因了就是这个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
}
},
看下修改后:
其它样式调整也可以用该方法找到对应的span后修改style值实现,只是最后要把font-size='x-small'的样式清空。
更多前端技术文章请关注微信公众号:xiaoyuanlianer666 掘金账号:小圆脸儿