本文源自道招网的CKEditor系列(七)编辑器工具栏根据宽度自动折叠
刚才看了看上一篇写CKEditor的文章是在今年的一月份,现在轮到我们的设计师对编辑器下手了。我们回顾下现在的编辑器长什么样子。
需求
我们客户端默认窗口尺寸下,会出现排,并且第二排的这些功能使用频次相对较低,为了尽可能的增大用户的操作区域,所以决定做如下改动:
-
将居左对齐、居中对齐、居右对齐改成三合一的功能
-
将频次使用率低的有序列表和无序列表调整至最后面
-
根据可用宽度收起一行展示不下的工具栏图标
有这个需求,经验告诉我官方大概率不会有这种配置,但是还是先去官网和Google上搜搜吧。 最终发现官方有个toolbarCanCollapse
配置项,虽然有个折叠按钮的功能,但是远远不能满足需求。 对齐三合一功能更不可能有了,也没有找到。
效果
我们可以提前看下本文最后的实现效果是什么样的
CKEditor工具栏实现原理
# toolbar/plugin.js
editor.on( 'uiSpace', function( event ) {
if ( event.data.space != editor.config.toolbarLocation )
return;
// Create toolbar only once.
event.removeListener();
editor.toolbox = new toolbox();
...
var toolbars = editor.toolbox.toolbars,
toolbar = getToolbarConfig( editor ),
toolbarLength = toolbar.length;
for ( var r = 0; r < toolbarLength; r++ ) {
var toolbarId,
toolbarObj = 0,
toolbarName,
row = toolbar[ r ],
lastToolbarInRow = row !== '/' && ( toolbar[ r + 1 ] === '/' || r == toolbarLength - 1 ),
items;
....
我们可以在源码中看到工具图标的html输出是在toolbar插件中完成,我们需要做的就是干预这一行为,我们就需要改动部分toolbar源码。
代码实现
为了叙述方便,在我一贯主张的非必要少改动源码的前提下,决定只对toolbar做如下改动约束:
- 使用自动折叠功能后就不再执行执行系统默认的折叠、分组逻辑
- 可以使用配置是否启用自动折叠,不启用的情况下,跟原功能保持一致
- 能用CSS解决的,尽量用CSS,JS配合CSS完成对应的DOM结构
DOM结构
大道至简,我们可以结合官方的实现这样设计
- 将工具栏的每一个图标信息放入一个标签内,官方用的A标签(普通按钮功能)或者SPAN标签(下拉列表功能),舍弃官方分组功能(会将多个A标签放入一个cke_toolgroup的SPAN标签,多个toolbar)
我们可以直接改成扁平化的DOM结构,这样更直观。
-
将折叠按钮作为一个普通的工具栏插件来实现
-
将收起的收起的功能图片统一放进一个div容器中,该容器通过display来控制显隐
确保收起的插件工具栏图标始终渲染出来是为了避免该插件内部操作工具栏图标时因找不到对应id的dom而报错
最终的DOM结构是这样的
.mail_toolbar_folder的div里面就是目前已经被折叠的工具栏图标了
折叠计算
- 我们先要知道需要从第几个工具栏图标开始折叠 先算出当前容器的宽度,获取到总共有多少个工具栏图标以及其对应宽度
sizeMap
(可以先提前测量后写死,多语言下图标宽度也是固定的,空间换时间),将能够在工具栏完整展示的图标放入toolbarArr数组中。
大致思路就是循环全部的工具栏图标数组,如果发现已经图标总宽度已经超过容器宽度了,当前循环就不要放入toolbarArr,还有检测下toolbarArr已有的图标加上折叠按钮图标宽度够吗,如果不够的话,就需要将toolbarArr最后一个弹出来,再次计算toolbarArr和折叠按钮图标宽度是否够用,不够用则继续弹出,直至宽度够为止。
部分代码如下
var toolbar = editor.toolbar;
var sizeMap = editor.config.customToolbarSizeMap;
var totalIndex = -1;
var toolbarArr = [];
for ( var r = 0; r < toolbar.length; r++ ) {
var row = toolbar[ r ];
var items = (row.items || row || []).slice();
if (items[0] && items[0].name === 'mailautofolding') {
foldItem = items.shift();
}
for ( var i = 0; i < items.length; i++ ) {
totalIndex ++;
var item = items[i];
var totalWidth = this.sumList(toolbarArr);
if (totalWidth + sizeMap[item.name] > containerWidth) { // 加上当前icon宽度就超了
shouldFoldIndex = totalIndex - 1; // 回到上一个index
console.log('exceed ~ ', shouldFoldIndex, items[shouldFoldIndex] && [shouldFoldIndex].name, this.sumList(toolbarArr));
// 已存在的加上折叠icon仍不够则需要继续回退
while (shouldFoldIndex > -1 && this.sumList(toolbarArr) + sizeMap.mailautofolding > containerWidth) {
shouldFoldIndex --;
console.log('back ~ ',shouldFoldIndex, items[shouldFoldIndex] && items[shouldFoldIndex].name);
toolbarArr.pop();
}
break;
} else {
toolbarArr.push({
name: item.name,
size: sizeMap[item.name]
})
}
}
}
- 实现折叠效果 我们现在能够通过宽度计算得知当前宽度下能完整展示多少个工具栏图标了,接下来我们需要把一排展示不下的图标挪到折叠区内。
大致思路: 宽度不够时将工具栏容器(.mail_toolbar_folder_container)内 超出的图标移动到折叠容器(.mail_toolbar_folder)内 宽度够时将折叠容器(.mail_toolbar_folder)内图标移动到工具栏容器(.mail_toolbar_folder_container)内 根据折叠容器内是否有收起的图片来判断折叠指示器是否需要展示
部分代码如下
function reRangeToolbar() {
var data = this.checkWidth(CKEDITOR.instances.ck4);
var shouldFoldIndex = data.shouldFoldIndex;
var container = document.querySelector('.mail_toolbar_folder_container');
var indicator = document.querySelector('.cke_button__mailautofolding');
var folder = document.querySelector('.mail_toolbar_folder');
var containerLenFix = 2; // mail_toolbar_folder cke_button__mailautofolding
console.log('reRangeToolbar checkWidth ~ ', data);
while (shouldFoldIndex > -1 && container.children.length - containerLenFix - 1 >= shouldFoldIndex) {
var item = container.children[container.children.length - containerLenFix - 1];
if (item.classList.contains('mail_toolbar_folder') || item.classList.contains('cke_button__mailautofolding')) {
continue;
}
folder.insertAdjacentElement('afterbegin', item);
}
while (folder.children.length >0 && (shouldFoldIndex < 0 || shouldFoldIndex > -1 && container.children.length - containerLenFix - 1 < shouldFoldIndex) ) {
var item = folder.children[0];
indicator.insertAdjacentElement('beforebegin', item);
}
if (folder.children.length > 0) {
indicator.style.display = '';
} else {
indicator.style.display = 'none';
}
this.toggleFolderStatus(true);
}
触发折叠计算时机
我们需要能监听到编辑器工具的宽度变化,自己也尝试过其它较新颖的API来做宽度变化监测,不是没效果,就是效果不理想,div之类有没有resize之类的事件,resize是window上面才能监听的事件。 刚好CKEditor编辑器的编辑器区域是放在iframe中的,基于这一点我们就可以监听这个window的resize事件了,还是resize靠谱,我们可以直接在折叠插件里面加上这部分监听逻辑。
afterInit: function (editor) {
editor.on('contentDom', function () {
var editable = editor.editable();
editor.document.getWindow().$.addEventListener('resize', function (evt) {
CKEDITOR.mail.reRangeToolbar();
}, null, null, 1);
// 偶发情况下工具并未折叠,所以延时再补充触发一次宽度计算
clearTimeout(timeId);
timeId = setTimeout(function () {
CKEDITOR.mail.reRangeToolbar();
}, 500)
})
}
总结
使用第三方库的时候如果现状不满足需求,先看官方有没有对应的配置或者解决方案,实在不行才是自己动手解决,先理解需求,然后拆分解决步骤,一步一步就把问题解决了。