对kityminder使用的一些思考与总结

背景

自从上家公司被裁后,家里蹲了近两个月,苦于自己学历大专,又不是对口专业,所以投递的简历基本上要么已读不回、要么不读。偶然的一次机会,朋友圈有人推荐需要招前端的项目,奔着试试的心态就投递了简历,结果挺意外的约到了面试机会。后续面试过程中了解到当前项目是工业互联网领域的代表性项目。在二面时聊到了如果我进到项目组会交给我一个重难点类似于实现xmind各种交互,要我自己可以hold住。

尝试

考虑到当下市场环境的的确确太差了,不出意外的我进了这个项目组,熟悉项目过程中,发现他们之前应该调研过xmind的类似实现方式就是使用kityminder,对于这个库我所熟知的寥寥无几,而项目组的开发方式是使用桌面云来进行开发,github无法进行访问,下班回家后我在github上面找到相应的开源库,了解到如果需要对这种脑图进行二次开发,则必须要用到kityminder-corekitymidner-editor两个核心包。

这个项目用的umi.js框架搭建的 umijs.org/

迁移

迁移方式结合umi.js当中的配置项headScripts引入编译后js资源kitminder.core.min.js和kity.min.js以及hotbox.js。至于kityminder-editor我直接是通过放在src目录下的lib文件夹,原因是因为kityminder-editor的js资源会用到angular与jquey等资源,而项目当中用到的是react框架,而其核心主要是svg来绘制脑图所以就没必要引入过多没必要的资源。引入方式示例可以参考github.com/fex-team/ki...

初始化

脑图初始化通过KMEditor类传入dom节点来为dom元素绑定一系列的类方法来实现

js 复制代码
function KMEditor(selector) {
        this.selector = selector;
        for (var i = 0; i < runtimes.length; i++) {
            if (typeof runtimes[i] == 'function') {
                runtimes[i].call(this, this);
            }
        }
    }
    KMEditor.assemble = assemble;

    assemble(require('./runtime/container'));
    assemble(require('./runtime/fsm'));
    assemble(require('./runtime/minder'));
    assemble(require('./runtime/receiver'));
    assemble(require('./runtime/hotbox'));
    assemble(require('./runtime/input'));
    assemble(require('./runtime/clipboard-mimetype'));
    assemble(require('./runtime/clipboard'));
    assemble(require('./runtime/drag'));
    assemble(require('./runtime/node'));
    assemble(require('./runtime/history'));
    assemble(require('./runtime/jumping'));
    assemble(require('./runtime/priority'));
    assemble(require('./runtime/progress'));
二次改造重难点Render、Layout
  1. kity.js暴露了模块的创建createClass和扩展extendClass(如Minder、MinderNode)
  2. Minder、MinderNode分别暴露了很多API具体可参考github.com/fex-team/ki...
  3. 节点创建:
js 复制代码
createNode: function(textOrData, parent, index) {
            var node = new MinderNode(textOrData);
            this.fire('nodecreate', {
                node: node,
                parent: parent,
                index: index
            });
            this.appendNode(node, parent, index);
            return node;
        },

单个节点渲染:

js 复制代码
renderNode: function(node) {
                var rendererClasses = this._rendererClasses;
                var i, latestBox, renderer;

                if (!node._renderers) {
                    createRendererForNode(node, rendererClasses);
                }

                this.fire('beforerender', {
                    node: node
                });

                node._contentBox = new kity.Box();

                node._renderers.forEach(function(renderer) {

                    // 判断当前上下文是否应该渲染
                    if (renderer.shouldRender(node)) {

                        // 应该渲染,但是渲染图形没创建过,需要创建
                        if (!renderer.getRenderShape()) {
                            renderer.setRenderShape(renderer.create(node));
                            if (renderer.bringToBack) {
                                node.getRenderContainer().prependShape(renderer.getRenderShape());
                            } else {
                                node.getRenderContainer().appendShape(renderer.getRenderShape());
                            }
                        }

                        // 强制让渲染图形显示
                        renderer.getRenderShape().setVisible(true);

                        // 更新渲染图形
                        latestBox = renderer.update(renderer.getRenderShape(), node, node._contentBox);

                        if (typeof(latestBox) == 'function') latestBox = latestBox();

                        // 合并渲染区域
                        if (latestBox) {
                            node._contentBox = node._contentBox.merge(latestBox);
                            renderer.contentBox = latestBox;
                        }
                    }

                    // 如果不应该渲染,但是渲染图形创建过了,需要隐藏起来
                    else if (renderer.getRenderShape()) {
                        renderer.getRenderShape().setVisible(false);
                    }

                });

                this.fire('noderender', {
                    node: node
                });
            }
        };
    }

this.fire('noderender', { node: node });表示注册了一个事件noderender,并传入一个node节点,如果想在渲染完成后布局前执行一些操作可以监听noderender事件,Minder.on('noderender', { node })

节点批量渲染:

js 复制代码
renderNodeBatch: function(nodes) {
                var rendererClasses = this._rendererClasses;
                var lastBoxes = [];
                var rendererCount = 0;
                var i, j, renderer, node;

                if (!nodes.length) return;

                for (j = 0; j < nodes.length; j++) {
                    node = nodes[j];
                    if (!node._renderers) {
                        createRendererForNode(node, rendererClasses);
                    }
                    node._contentBox = new kity.Box();
                    this.fire('beforerender', {
                        node: node
                    });
                }

                // 所有节点渲染器数量是一致的
                rendererCount = nodes[0]._renderers.length;

                for (i = 0; i < rendererCount; i++) {

                    // 获取延迟盒子数据
                    for (j = 0; j < nodes.length; j++) {
                        if (typeof(lastBoxes[j]) == 'function') {
                            lastBoxes[j] = lastBoxes[j]();
                        }
                        if (!(lastBoxes[j] instanceof kity.Box)) {
                            lastBoxes[j] = new kity.Box(lastBoxes[j]);
                        }
                    }

                    for (j = 0; j < nodes.length; j++) {
                        node = nodes[j];
                        renderer = node._renderers[i];

                        // 合并盒子
                        if (lastBoxes[j]) {
                            node._contentBox = node._contentBox.merge(lastBoxes[j]);
                            renderer.contentBox = lastBoxes[j];
                        }

                        // 判断当前上下文是否应该渲染
                        if (renderer.shouldRender(node)) {

                            // 应该渲染,但是渲染图形没创建过,需要创建
                            if (!renderer.getRenderShape()) {
                                renderer.setRenderShape(renderer.create(node));
                                if (renderer.bringToBack) {
                                    node.getRenderContainer().prependShape(renderer.getRenderShape());
                                } else {
                                    node.getRenderContainer().appendShape(renderer.getRenderShape());
                                }
                            }

                            // 强制让渲染图形显示
                            renderer.getRenderShape().setVisible(true);

                            // 更新渲染图形
                            lastBoxes[j] = renderer.update(renderer.getRenderShape(), node, node._contentBox);
                        }

                        // 如果不应该渲染,但是渲染图形创建过了,需要隐藏起来
                        else if (renderer.getRenderShape()) {
                            renderer.getRenderShape().setVisible(false);
                            lastBoxes[j] = null;
                        }
                    }
                }

                for (j = 0; j < nodes.length; j++) {
                    this.fire('noderender', {
                        node: nodes[j]
                    });
                }
            },

节点渲染完成后则会开始进行布局操作,而布局会根据模板来决定,kityminder提供了以下几种模板(default、filetree、right、fish-bone等);而Layout同样提供了几种不同的布局方式(btree、filetree、fish-bone-master等);一般项目当中设置模板会通过Minder.useTemplate()来进行设置,而布局则通过setLayout()来实现。

布局:

js 复制代码
Layout.register(name, kity.createClass({

            base: Layout,

            doLayout: function(parent, children) {

                var pbox = parent.getContentBox();

                if (axis == 'x') {
                    parent.setVertexOut(new kity.Point(pbox[name], pbox.cy));
                    parent.setLayoutVectorOut(new kity.Vector(dir, 0));
                } else {
                    parent.setVertexOut(new kity.Point(pbox.cx, pbox[name]));
                    parent.setLayoutVectorOut(new kity.Vector(0, dir));
                }

                if (!children.length) {
                    return false;
                }

                children.forEach(function(child) {
                    var cbox = child.getContentBox();
                    child.setLayoutTransform(new kity.Matrix());

                    if (axis == 'x') {
                        child.setVertexIn(new kity.Point(cbox[oppsite[name]], cbox.cy));
                        child.setLayoutVectorIn(new kity.Vector(dir, 0));
                    } else {
                        child.setVertexIn(new kity.Point(cbox.cx, cbox[oppsite[name]]));
                        child.setLayoutVectorIn(new kity.Vector(0, dir));
                    }
                });

                this.align(children, oppsite[name]);
                this.stack(children, oppsite[axis]);

                var bbox = this.getBranchBox(children);
                var xAdjust = 0, yAdjust = 0;

                if (axis == 'x') {
                    xAdjust = pbox[name];
                    xAdjust += dir * parent.getStyle('margin-' + name);
                    xAdjust += dir * children[0].getStyle('margin-' + oppsite[name]);

                    yAdjust = pbox.bottom;
                    yAdjust -= pbox.height / 2;
                    yAdjust -= bbox.height / 2;
                    yAdjust -= bbox.y;
                } else {
                    xAdjust = pbox.right;
                    xAdjust -= pbox.width / 2;
                    xAdjust -= bbox.width / 2;
                    xAdjust -= bbox.x;

                    yAdjust = pbox[name];
                    yAdjust += dir * parent.getStyle('margin-' + name);
                    yAdjust += dir * children[0].getStyle('margin-' + oppsite[name]);
                }

                this.move(children, xAdjust, yAdjust);
            },
        }));

布局的计算方式也是通过父盒子与子盒子的box来设置,如果要改变默认的节点布局方式要考虑两点:

  1. 将当前节点相对于父节点如何transform变换
  2. 将变换后的节点与父节点进行连线,设置pathData
相关推荐
子非鱼92119 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
想退休的搬砖人33 分钟前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript
清灵xmf1 小时前
揭开 Vue 3 中大量使用 ref 的隐藏危机
前端·javascript·vue.js·ref
蘑菇头爱平底锅1 小时前
十万条数据渲染到页面上如何优化
前端·javascript·面试
2301_801074151 小时前
TypeScript异常处理
前端·javascript·typescript