资讯详情

纯CSS族谱树:零依赖实现家谱可视化

📅 2026/10/27 8:26:02 | 华诺云谱 👁 阅读
纯CSS族谱树:零依赖实现家谱可视化
简介这份资源面向前端初学者与Web开发者提供一套纯CSS实现的族谱树形结构示例用于解决层次化数据的可视化展示问题可迁移至家谱、企业组织架构、文件目录树等场景。压缩包内仅1个html文件约2KB将结构与样式集中呈现便于直接打开查看效果并在此基础上修改。资源围绕display、position、float等布局属性以及:before、:after伪元素绘制连接线、:hover增强交互等知识点展开同时涉及无限级节点的递归嵌套与间距定位思路为二次开发中的自定义配色、展开折叠与响应式适配留出空间。目前已有1273人学习适合希望以较小成本理解CSS树形布局原理、并快速搭建原型的中级学习者参考。1. 纯 CSS 族谱树不写一行 JS 也能把家谱画明白前阵子帮某实验室整理一份家族关系数据需求很朴素把三四代人的亲属关系画成一张能直接嵌进静态页面的树形图。第一反应是上 D3 或者 ECharts结果对方一句“页面是纯静态托管不想引第三方库”把我堵回来了。后来翻到一份纯 CSS 族谱树形结构的实现思路核心就三件事用嵌套ul/li表达层级、用伪元素::before/::after画连接线、用 flex 做水平排布。它解决的是“零依赖、可打印、可被爬虫读”的族谱展示问题适合做家谱、族谱、组织架构这类静态展示页的前端同学也适合想搞懂 CSS 画线原理的人拿来拆。下面按我实际复现的顺序讲从结构怎么搭到线怎么连再到哪些地方会翻车。2. 结构先行用嵌套列表把族谱的层级关系钉死纯 CSS 方案能不能成立八成取决于 HTML 结构选得对不对。族谱本质是一棵多叉树每个人是一个节点子女是子节点配偶是横向关联。CSS 没有“父选择器”能反向找爹所以层级必须靠 DOM 的嵌套关系提前表达清楚样式只是把这层关系“画”出来。2.1 为什么选 ul/li 而不是 div 套 divul/li的语义就是“列表项”浏览器默认知道li是ul的子项这对屏幕阅读器和 SEO 都友好家谱这种带强语义的内容尤其吃这一套。更实际的原因是li天然是块级、天然可以嵌套、天然有list-style可以一键清掉省掉一堆display声明。用div也能做但你得自己维护“谁是父谁是子”的类名节点一多就乱。我一般会约定三层结构最外层ul.tree是根每个li代表一个人li内部再放一个.node承载姓名、生卒年这些信息子女继续用嵌套ul挂在li里。这样“人”和“关系”是分开的后面调样式不会互相打架。ul classtree li div classnode span classname始祖/span span classmeta1880 - 1952/span /div ul li div classnodespan classname长子/span/div ul lidiv classnodespan classname长孙/span/div/li lidiv classnodespan classname次孙/span/div/li /ul /li li div classnodespan classname次子/span/div /li /ul /li /ul这段结构的关键点在于ul只负责“分组”li只负责“一个节点 它的后代”。.node是唯一承载视觉样式的容器这样你后面想给某个人加高亮、加配偶标记只动.node就行不会影响连接线的定位。注意子女的ul是li的直接子元素不是.node的子元素——这一点决定了连接线从哪画出来写反了线会从名字框里穿过去。2.2 清默认样式与基础排布浏览器给ul的默认padding-left和li的list-style是画树的第一道坎不清掉的话每个层级都会往右缩一截树会歪成斜的。基础重置我一般这么写.tree, .tree ul { list-style: none; margin: 0; padding: 0; } .tree ul { display: flex; justify-content: center; gap: 24px; padding-top: 32px; position: relative; } .tree li { display: flex; flex-direction: column; align-items: center; position: relative; padding: 0 8px; }display: flex加在ul上让同一层的兄弟姐妹水平排开justify-content: center保证子节点相对父节点居中——这是族谱“父在上、子在下、子居中”的视觉基础。padding-top: 32px是给连接线留的垂直空间线要从父节点底部走到子节点顶部没这段空隙线会贴脸。li上的position: relative是后面伪元素定位的锚点漏了它线会跑到页面左上角去这是新手最常见的翻车点之一。gap控制同层节点间距族谱人多的时候这个值要调大不然名字会挤在一起。我一般按最长姓名的宽度估gap至少给到 24px四代以上建议 32px 起步。3. 连接线怎么画伪元素定位的四个关键参数结构搭好后页面上的节点是散着的没有线就看不出谁是谁的孩子。纯 CSS 画线的全部秘密就在::before和::after上一个画“竖线”父到子一个画“横线”兄弟姐妹之间的桥。这两根线的位置、长度、显隐条件决定了整棵树看起来是规整还是歪瓜裂枣。3.1 竖线从父节点底部垂到子层竖线的逻辑是每个li的顶部往上伸一根线连到它所属的那条横线上。写法如下.tree li::before { content: ; position: absolute; top: 0; left: 50%; width: 2px; height: 32px; background: #b0b7c3; transform: translateX(-50%); }top: 0让线从li的顶边开始height: 32px正好等于父级ul的padding-top这样线刚好填满父子之间的空隙。left: 50%加translateX(-50%)是把线水平居中到节点正上方少了transform线会偏半个线宽细看能看出来。width: 2px是线粗打印场景建议给到 1px 并配深色屏幕上 2px 更清楚。这里有个容易忽略的点根节点不该有向上的竖线否则树顶会多出一截“天线”。所以实际项目里我会给根li单独加个类或者用.tree li::before { display: none; }干掉它。3.2 横线兄弟姐妹之间的桥靠首尾节点断开横线是纯 CSS 族谱里最绕的一根。它要连接同一层的所有兄弟姐妹但最左和最右的节点不能把线伸出去否则会多出两截“断头线”。常见做法是给每个li画一根横跨自身宽度的线再用:first-child和:last-child各砍掉一半.tree li::after { content: ; position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: #b0b7c3; } .tree li:first-child::after { left: 50%; width: 50%; } .tree li:last-child::after { width: 50%; } .tree li:only-child::after { display: none; }逻辑说明默认每个li顶部画满宽横线first-child把起点挪到中点、宽度砍半last-child把宽度砍半这样最左节点只有右半段线、最右节点只有左半段线中间节点是整段拼起来就是一条连续的桥。only-child是独生子女没有兄弟姐妹横线直接不画否则会多出一小截悬空的线头。参数上横线的top必须和竖线的top对齐都是 0否则横竖线接不上会出现 12px 的错位放大看很明显。height和竖线的width保持一致视觉上才是一根线。3.3 父节点到横线的“下引线”上面两根线解决的是“子层内部”的连接还差一段父节点底部到子层横线之间的那根竖线。这段线挂在父li的ul上更稳.tree ul::before { content: ; position: absolute; top: 0; left: 50%; width: 2px; height: 32px; background: #b0b7c3; transform: translateX(-50%); }它和li::before长得一样但挂载点不同ul::before画的是“父到子层横线”这一段li::before画的是“子层横线到每个子节点”那一段。两段接起来整条垂直通路才完整。根ul没有父节点所以.tree ul::before或者根层要排除掉不然树顶又会多一截线。3.4 配偶节点横向关联的另一种画法族谱里夫妻是横向并列的不是父子关系硬塞进嵌套结构会破坏层级。我的处理是把配偶作为.node内部的第二个span用 flex 横排中间加一根短横线表示婚姻关系.node { display: flex; align-items: center; gap: 12px; padding: 8px 16px; border: 1px solid #d5dae3; border-radius: 6px; background: #fff; } .node .spouse { border-left: 2px solid #b0b7c3; padding-left: 12px; }这样配偶不参与树的层级计算不会影响连接线定位视觉上又和主节点绑在一起。如果要做“再婚”“多配偶”就在.node里继续加spangap会自动排开不用改树的逻辑。这是纯 CSS 方案比 JS 库灵活的地方——结构即样式改结构不用改渲染代码。4. 避坑与排查五个让我返工的血泪记录纯 CSS 族谱看着简单真上手调的时候坑一个接一个而且 CSS 没有报错错了只能靠肉眼找。下面五条是我实际踩过、并且帮别人排查时反复出现的按“现象 → 原因 → 解决”记下来。4.1 整棵树往右歪越深越偏现象根节点在页面中间但第三代、第四代整体向右漂移树看起来是斜的。原因某一层ul的默认padding-left没清干净或者某个li上残留了margin。浏览器默认ul有 40px 左内边距嵌套几层就累加几层。解决全局重置.tree ul { padding: 0; margin: 0; }并且用开发者工具逐层检查li的盒模型把非预期的margin归零。排查时可以在li上临时加outline: 1px solid red一眼就能看出哪层盒子多出来了。4.2 横线在独生子女处多出一截现象只有一个孩子的节点子层横线左边或右边多出一小段悬空的线。原因只写了:first-child和:last-child没处理:only-child。独生子女同时是 first 和 last两条规则叠加后宽度算错。解决补.tree li:only-child::after { display: none; }并且把它放在 first/last 规则之后靠层叠顺序覆盖。顺序写反了不生效这是 CSS 层叠的玄学血泪经验。4.3 连接线穿透名字框现象竖线从节点文字中间穿过去看起来像划掉名字。原因子女的ul写在了.node内部导致ul::before的定位锚点变成.node线从框内开始画。解决把子女ul移到li的直接子级和.node平级。结构上li .node ul样式上ul::before的top才会以li为基准。改完记得检查li有没有overflow: hidden有的话线会被裁掉。4.4 打印时线消失或变灰现象屏幕上正常一按打印预览连接线没了或者淡到看不见。原因浏览器打印默认不输出背景色而线是用background画的另外浅灰色#b0b7c3在黑白打印下对比度不够。解决给线加media print覆盖改用border画线或者加-webkit-print-color-adjust: exact;颜色换成#666以上。打印场景线宽给 1px2px 在纸上会糊成一片。4.5 节点一多就横向溢出现象同层超过七八个节点后整棵树撑出容器出现横向滚动条。原因flex默认不换行gap又固定节点总宽超过容器就溢出。解决给最外层容器加overflow-x: auto让树自己滚或者用flex-wrap: wrap让同层折行——但折行会破坏横线的连续性族谱场景一般不折宁可滚动。真要压缩就减小.node的padding和gap别动字号字号小了打印看不清。5. 进阶用 CSS 变量把族谱做成可配置的模板上面这套写死参数的版本能跑但每换一个家族就得改一遍颜色、间距、线宽维护成本高。我后来的习惯是把所有可调项抽成 CSS 变量挂在.tree上换主题只改变量值结构一行不动。这一步做完这份纯 CSS 族谱才算真正能复用。.tree { --line-color: #b0b7c3; --line-width: 2px; --level-gap: 32px; --node-gap: 24px; --node-radius: 6px; } .tree ul { gap: var(--node-gap); padding-top: var(--level-gap); } .tree li::before, .tree ul::before { width: var(--line-width); height: var(--level-gap); background: var(--line-color); } .tree li::after { height: var(--line-width); background: var(--line-color); } .node { border-radius: var(--node-radius); }变量化的好处在排查时特别明显线接不上先怀疑--level-gap和height没同步颜色不对只查--line-color一处。以前改一个间距要翻五六个选择器现在改一行变量全树跟着动。这套写法还有个隐性收益——同一页面要放两棵不同风格的树比如父系和母系给两个.tree容器设不同的变量值就行不用复制两套 CSS。验证方法我一般走三步先在浏览器里把窗口从 1280px 拖到 375px看横向溢出和线错位再用打印预览检查黑白下的对比度最后把 HTML 丢进一个不带任何样式的空白页确认结构本身是语义完整的——这一步是纯 CSS 方案相对 JS 渲染最大的底气样式挂了内容还在。有个细节值得单独说变量默认值别写在:root上写在.tree上。写:root会污染全局页面里其他组件用到同名变量就串了。我吃过这个亏一个--gap把导航栏的间距也改了查了半天才发现是变量作用域的问题。从那以后我每次抽变量都强制加组件前缀--tree-开头宁可名字长一点也不给后面埋雷。如果你手上正好有一份家谱、族谱数据要落到静态页面上又不想为了一张图引一整个图表库这套纯 CSS 结构值得直接拿去改。把 HTML 结构按你的数据层级套进去变量按你的视觉规范调一遍剩下的就是填名字的体力活。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑