一个简单的JS/CSS3组织架构生成DEMO
发布/
小网
数据源/原创
用CSS3 JAVASCRIPT 动态生成组织架构的DEMO
因业务需要,需要做一个类似组织架构的树型结构生成工具,效果如下:

考虑到数据内容会动态变化,而且节点层级是动态的。用canvas生成难度和复杂度相对较大,而且要各种计算,后来就直接用css3来实现。用CSS3来实现就可以简单的解决前面的几个动态因素引起的问题,实现起来还是挺简单的。具体见下面的示例代码(需要支持flex的浏览器环境)。
html{
background-color: #f3f3f3;
}
body{
color: #333;
font-size: .24rem;
}
html.fixedpage{
width: 100%;
height: 100%;
max-height: 100%;
/*overflow: hidden;*/
display: block;
}
html.fixedpage body{
width: 100%;
height: 100%;
max-height: 100%;
/*overflow: hidden;*/
display: block;
}
html.fixedpage .app-body{
overflow: hidden;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
display: block;
}
html.fixedpage.fxbottom .app-body{
height: -webkit-calc(100% - .98rem);
height: -moz-calc(100% - .98rem);
height: -ms-calc(100% - .98rem);
height: calc(100% - .98rem);
}
html.fixedpage.fxtop .app-body{
height: -webkit-calc(100% - .88rem);
height: -moz-calc(100% - .88rem);
height: -ms-calc(100% - .88rem);
height: calc(100% - .88rem);
}
html.fixedpage.fxboth .app-body{
height: -webkit-calc(100% - .88rem - .98rem);
height: -moz-calc(100% - .88rem - .98rem);
height: -ms-calc(100% - .88rem - .98rem);
height: calc(100% - .88rem - .98rem);
}
.app-header{
width: 100%;
position: relative;
z-index: 20;
}
.app-body{
width: 100%;
position: relative;
z-index: 10;
}
.app-footer{
width: 100%;
position: relative;
z-index: 20;
}
html.fixedpage{
width: initial;
height: initial;
max-height: initial;
display: initial;
}
html.fixedpage body{
width: initial;
height: initial;
max-height: initial;
display: initial;
}
html.fixedpage .app-body{
overflow: auto;
-webkit-overflow-scrolling: touch;
display: block;
}
.root{
/*position: absolute;
left: 0;
top: 0;*/
position: relative;
margin-top: .40rem;
}
.node{
margin: 0 .20rem;
position: relative;
}
.root > .node::before{
content: none!important;
}
.root > .node > .node-data::before{
content: none!important;
}
.node::before{
content: "";
display: block;
height: .20rem;
width: -webkit-calc(100% + .40rem);
border-top: 1px solid #565596;
position: absolute;
top: -.20rem;
}
.node:first-child::before{
content: "";
display: block;
height: .20rem;
width: -webkit-calc(50% + .20rem);
/*border-left: 1px solid #565596;*/
border-top: 1px solid #565596;
position: absolute;
top: -.20rem;
left: 50%;
}
.node:last-child::before{
content: "";
display: block;
height: .20rem;
width: -webkit-calc(50% + .20rem);
/*border-right: 1px solid #565596;*/
border-top: 1px solid #565596;
position: absolute;
top: -.20rem;
right: 50%;
}
.node:only-child::before{
content: "";
border-top: 0;
border-right: 0;
margin-left: -1px;
}
.node-data{
border: 1px solid #565596;
background-color: #fff;
color: #565596;
width: 2.00rem;
text-align: center;
border-radius: .10rem;
position: relative;
}
.node-data::before{
content: "";
display: block;
height: .20rem;
width: 0;
border-left: 1px solid #565596;
position: absolute;
top: -.20rem;
left: 50%;
margin-left: -1px;
}
.node-data::after{
content: "";
display: block;
height: .20rem;
width: 0;
border-left: 1px solid #565596;
position: absolute;
top: 100%;
left: 50%;
margin-left: -1px;
}
.node-data.no-children::after{
content: none;
}
.node-data h2{
font-weight: normal;
font-size: .24rem;
line-height: 1.7;
}
.node-data p{
font-size: .24rem;
color: #666;
line-height: 1.5;
}
.node-children{
margin-top: .40rem;
position: relative;
}
/*.node-children::before{
content: "";
display: block;
width: 50%;
height: .20rem;
position: absolute;
top: -.20rem;
left: 25%;
border: 1px solid #565596;
border-bottom: 0;
}*/ <script type="text/template" id="tpl0">
<~
var node = rd.node;
var children = rd.children;
~>
<div class="node flexbox middle center vertical nowrap">
<div class="node-data<~=children && children.length > 0 ? '' : ' no-children'~>">
<h2><~=node.userName~></h2>
<p>ID:<~=node.money~></p>
</div>
<div class="node-children flexbox top center">
<~
for(var i = 0; i < children.length; i++){
var child = children[i];
~>
<div class="node flexbox middle center vertical nowrap">
<div class="node-data<~=child.children && child.children.length > 0 ? '' : ' no-children'~>">
<h2><~=child.node.userName~></h2>
<p>ID:<~=child.node.money~></p>
</div>
<div class="node-children flexbox top center">
<~=_children(child.children)~>
</div>
</div>
<~
}
~>
</div>
</div>
</script>
<div class="root flexbox top left">
</div>var data = {
"node": {
"userName": "小网UU",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
}
]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
},
{
"node": {
"userName": "1234",
"money": 1234
},
"children": [ ]
}
]
}
]
}
]
}
]
}
]
};var oTemplateEngine = (function(){
var Util = {
/**
* 执行回调
* @param Object handler {Function callback, Array args, Object context, int delay}
* @param * __args 附加参数,些参数将会置于 handler.args 之前
*/
execHandler : function(handler, __args){
if(handler && handler instanceof Object){
var callback = handler.callback || null;
var args = [].concat(handler.args || []);
var context = handler.context || null;
var delay = handler.delay || -1; if(__args){
__args = [].concat(__args); args = __args.concat(args);
} if(callback && callback instanceof Function){
if(typeof(delay) == "number" && delay >= 0){
if(callback.tid){
clearTimeout(callback.tid);
callback.tid = undefined;
} return (callback.tid = setTimeout(function(){
callback.apply(context, args);
}, delay));
}else{
return callback.apply(context, args);
}
}
}
},
getTime: function(){
return Date.now ? Date.now() : (new Date().getTime());
}
};
//options::start 启始标签
//options::close 结束标签
//options::handle 渲染后回调函数
//options::root 模板渲染时的本地对象空间
var _Template = function(name, options){
this.name = name;
this.options = options;
this.start = options.start || "<%";
this.close = options.close || "%>";
this.handle = options.handle || null;
this.root = options.root || "obj";
}; _Template.TPLCache = {};
_Template.Cache = {}; _Template.prototype = {
/**
* 模板渲染
* @param boolean isDirect 是否为直接量,true: tplId为模板片断, false: tplId为模板容器ID
* @param String tplId 模板或模板容器ID
* @param Object metaData 模板数据
* @param Object handle 渲染回调
* @return Object ret {Object global, Object local}
*/
render: function(isDirect, tplId, metaData, handle){
var tpl = (true === isDirect ? tplId : (_Template.TPLCache[tplId] || (_Template.TPLCache[tplId] = $("#" + tplId).html()))); return (function(_t, str, data, callback){
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
var startTime = Util.getTime();
var chr = function(str){
var tmp = "";
for(var i = 0, size = str.length; i < size; i++){
tmp += "\\" + str.charAt(i);
} return tmp;
};
var template = str;
var fn = new Function(
_t.root,
"var p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){}
"with(" + _t.root + "){p.push('" +
//------------------------------------
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split(_t.start).join("\t")
.replace(new RegExp("((^|" + chr(_t.close) + ")[^\\t]*)'", "g"), "$1\r")
.replace(new RegExp("\\t=(.*?)" + chr(_t.close), "g"), "',$1,'")
.split("\t").join("');")
.split(_t.close).join("p.push('")
.split("\r").join("\\'")
//------------------------------------
+ "');}return p.join('');"
); // Provide some basic currying to the user
var result = data ? fn( data ) : str;
var endTime = Util.getTime();
var elapsedTime = endTime - startTime;
var o = {
"result": result,
"elapsedTime": elapsedTime,
"template": template,
"metaData": metaData
};
var ret = {
"global": undefined,
"local": undefined
}; if(_t.handle){
ret["global"] = Util.execHandler(_t.handle, [o]); //全局
}
if(callback){
ret["local"] = Util.execHandler(callback, [o]); //局部
} return ret;
})(this, tpl, metaData, handle);
}
}; return {
"version": "R17B0817",
getTemplate: function(name, options){
var _t = _Template.Cache[name] || (_Template.Cache[name] = new _Template(name, options || {})); return {
render: function(isDirect, tplId, metaData, handle){
var ret = _t.render(isDirect, tplId, metaData, handle || null);
var eng = this; eng["global"] = ret["global"];
eng["local"] = ret["local"]; return eng;
}
}
}
};
})();
/////////////////////////////////////////////////////////
function _children(items){
var size = items.length;
var html = ""; var ote = oTemplateEngine.getTemplate("ote", {
"start": "<~",
"close": "~>",
"root": "rd"
}); for(var i = 0; i < size; i++){
var item = items[i]; html += ote.render(false, "tpl0", item, {
callback: function(ret){
return ret.result;
}
}).local
} return html;
};
(function(){
var ote = oTemplateEngine.getTemplate("ote", {
"start": "<~",
"close": "~>",
"root": "rd"
}); ote.render(false, "tpl0", data, {
callback: function(ret){
$(".app-body").find(".root").html(ret.result);
}
}); setTimeout(function(){
var body = document.querySelector(".app-body");
var node = document.querySelector(".root > .node");
var rect = node.getBoundingClientRect();
var width = rect.right - rect.left; body.scrollLeft = (width / 2) - window.innerWidth / 2;
}, 60);
})();