家谱:修「线又错位」(我上一版的居中写法在库里不成立)+ 框宽收成头像宽 + 去掉悬停提示

用户 2026-10-03 反馈三条。**第一条是我上一版引入的回归,先说清楚**。

一、「画线又有点错位了」—— 上一版把居中从 transform 换成 `inset:0 + margin:auto`,
    这一步在**这个库里不成立**:
      含块是库的 `div.card`,它在 Cartesian 模式下只有 **1.8px 宽**
      (宽度由内部一个空 div 撑出来,卡片是溢出绘制的)。
      `inset:0` + `margin:auto` 的"居中"依赖"容器比元素宽" —— 容器只有 1.8px 时
      auto 外边距分不出任何空间,卡片直接被摆在容器左上角、整体**右移 53.1px**。
    实测(1600×1000、库缩放 0.9):**每一张**卡的中心都偏在节点中心右边 53.1px
    (是固定的布局偏差,不是随机抖动)→ 连线仍然画在节点上,于是全部错位。
    正解:`left/top: 50%` + **半个自身尺寸的负外边距**
    (`margin-left: calc(var(--kin-w) / -2)`)—— 位置完全由布局决定,
    **不经过 transform**,所以库那条 `:hover { transform: … }` 也顶不掉它。
    ⚠️ 负外边距用 calc 与尺寸共用变量,不写死 -59/-66(迟早脱节)。
    ⚠️ 这条路上已经栽过两次,两种错写法都别再回去:
       · `transform: translate(-50%,-50%)` → hover 时被库覆盖,卡片跳半张(53×58px);
       · `inset:0 + margin:auto` → 容器 1.8px 宽,卡片整体右移 53px、连线错位。

二、「外面那个框又变宽了;宽度和头像图片一致,下面显示名字就行了」:
    卡片 118 → **98px**(= 96 头像 + 左右各 1px 边框),横向 padding 归零,
    头像改 `width: 100%` 占满卡片内宽 —— 于是**边框刚好贴着头像**,
    框里不再有多余的横向空白;头像下面就是名字(+ 与我的称呼)。
    ⚠️ 98×116 与 JS 的 `setCardDim` 必须同源(库按它算间距与连线落点),
       所以两边都改用常量:CSS 的 `--kin-w/--kin-h` ↔ JS 的 `KIN_W/KIN_H`。

三、「鼠标放在名字上后显示了一行字:资料、以他为中心 —— 没必要,不要显示」:
    那两行是我上一版加的悬停提示(`.kin-hint`),**整个删掉**(模板 + CSS)。

验证:
  · 家谱可视化检查 29 → **34** 条全过,新增五条正是这次的守卫:
    ⑮ 每张卡正对节点中心(改坏时实测 dx=53.1)· ⑯ 框宽 = 头像宽(≤2px)
    · ⑯b 尺寸不是退化的 0(防止"一起变 0"也算过)· ⑰ 无 .kin-hint
    · ⑱ 头像下面是名字。
  · 反向验证:把居中改回 `inset:0 + margin:auto` → ⑮ 立刻红(dx=44.1),
    还原后复绿(断言不是永远绿的)。
  · 连线端点独立核对:12 个端点里 9 个落在卡片附近,另 3 个是
    **夫妻之间那段连接线**(画在两卡之间,本来就该在两卡之间)。
  · e2e 773/0 · 第 5 轮专项 91/0 · 几何 428/0 · 移动端 684/0 · 交互 97/0。
  · 部署脚本更新/新增静态断言:允许 transform 之外的居中方式但**禁止那两种错写法**
    (transform 居中 / inset:0)、必须有 `margin-left: calc(var(--kin-w) / -2)`、
    JS 与 CSS 的宽度必须一致、`kin-hint` 不许回来。
  · 线上两个文件 md5 与本地一致;kin-hint=0、--kin-w: 98px 在位。
This commit is contained in:
2026-10-03 17:49:10 +08:00
parent 6930191ac6
commit 218cdf74fc
2 changed files with 53 additions and 43 deletions
+44 -38
View File
@@ -621,12 +621,22 @@ textarea.input { min-height: 76px; resize: vertical; }
· 卡片不再整张是链接:只有 .kin-nm 是 <a>,右下角是独立的 <button>, · 卡片不再整张是链接:只有 .kin-nm 是 <a>,右下角是独立的 <button>,
两者分开,避免"点按钮却进了成员页"的误触。 两者分开,避免"点按钮却进了成员页"的误触。
卡片尺寸与 JS 的 setCardDim({w,h}) 必须一致(库按它算间距与连线落点)。 */ 卡片尺寸与 JS 的 setCardDim({w,h}) 必须一致(库按它算间距与连线落点)。 */
/* 卡片尺寸:**框宽 = 头像宽**(用户 2026-10-03 第 8 条:「外面那个框,宽度和头像图片一致,
下面显示名字就行了」)。
98 = 96(头像实际宽度,见 .kin-ph)+ 左右各 1px 边框 —— 即"边框刚好贴着头像",
框里不再有多余的横向空白(原来是 118 宽、比头像宽出 22px)。
⚠️ 横向 padding 必须是 0,否则头像(width:100%)会被压窄。
⚠️ 这个 98×116 必须与 JS 里 setCardDim({w,h}) **保持一致**:
库按它算卡片间距与连线落点,两边不一致连线就会歪。 */
.kin-card { .kin-card {
--kin-w: 98px;
--kin-h: 116px;
position: absolute; position: absolute;
display: flex; flex-direction: column; align-items: center; display: flex; flex-direction: column; align-items: center;
gap: 0; gap: 0;
width: 118px; height: 132px; width: var(--kin-w);
box-sizing: border-box; padding: 6px 6px 4px; height: var(--kin-h);
box-sizing: border-box; padding: 5px 0 4px;
text-align: center; text-align: center;
background: var(--surface); background: var(--surface);
border: 1px solid var(--line); border: 1px solid var(--line);
@@ -637,50 +647,54 @@ textarea.input { min-height: 76px; resize: vertical; }
overflow: hidden; overflow: hidden;
pointer-events: auto; /* 父 .card_cont 是 none,这里恢复 */ pointer-events: auto; /* 父 .card_cont 是 none,这里恢复 */
} }
/* ⚠️⚠️ 「鼠标放上去卡片会跳」的根因就在这里(用户 2026-10-03 反馈)。 /* ⚠️⚠️ 居中**绝不能**用 transform、也不能用 `inset:0 + margin:auto`。
库自带样式里有一条:`.f3 div.card:hover > div { transform: translate(0px,-2px); }` 两次踩坑记录(都是用户先发现的:一次"鼠标移上去框会动",一次"线又错位了"):
—— 我们的 .kin-card 正好是 `div.card` 的**直接子 div**,于是鼠标一进卡片, · 用 `transform: translate(-50%,-50%)`:库自带
这条规则就把**我们用来居中的 `transform: translate(-50%,-50%)` 整个覆盖掉**。 `.f3 div.card:hover > div { transform: translate(0,-2px) }` 会把居中 transform
实测(1600×1000 视口、库缩放 0.9):卡片从 translate(-59px,-66px) 变成 **整份覆盖** → 鼠标一进卡片整张卡跳 53×58px(半张卡)。
translate(0,-2px),整张卡**向右下跳 53.1 × 57.6 屏幕像素**(正好是半张卡)。 · 用 `inset: 0` + `margin: auto`:**在这里不成立** —— 含块是库的 `div.card`,
修法:不再用 transform 居中,改用 `inset: 0` + `margin: auto` 在 Cartesian 模式下它只有 **1.8px 宽**(宽度由内部一个空 div 撑出来),
—— 绝对定位 + inset 全 0 + margin auto 就是"在包含块里居中", auto 外边距在一个比卡片还窄的盒子里根本分不出空间,结果卡片整体**右移了 53.1px**
完全不依赖 transform,库那条规则再怎么覆盖也影响不到位置。 (实测每张卡的中心都偏在节点右边 53.1px)→ 连线与卡片错位。
(也因此**不需要**去覆盖库的 :hover —— 少一处 !important,少一处将来的漂移。) 正解:`left/top: 50%` + **半个自身尺寸的负外边距** —— 位置完全由布局决定,
⚠️ 别改回 `left/top: 50% + transform: translate(-50%,-50%)`:那正是这次的 bug。 */ 不经过 transform,库那条 :hover 规则怎么覆盖都影响不到。
⚠️ 负外边距必须写 `calc(var(--kin-*) / -2)`,与上面的尺寸共用同一个变量;
写成死数字(-59/-66)迟早会和尺寸脱节。 */
.kin-card { .kin-card {
inset: 0; left: 50%; top: 50%;
margin: auto; margin-left: calc(var(--kin-w) / -2);
margin-top: calc(var(--kin-h) / -2);
} }
/* ⚠️⚠️ 还要把库那条 `:hover` 位移**归零**(用户反馈的第二半)。 /* ⚠️ 还要把库那条 `:hover` 位移**归零**(用户反馈「鼠标放上去框会动」的另一半)。
库的 family-chart.css:517 有: 库的 family-chart.css:517 有:
`.f3 div.card:hover > div { transform: translate(0, -2px); }` `.f3 div.card:hover > div { transform: translate(0, -2px); }`
它不只覆盖居中(那已由上面的 inset+margin auto 解决),还会给卡片加一个 它除了覆盖居中,还会给卡片一个纯视觉的"上浮 2px"(实测 dy = -1.8px)。
纯视觉的"上浮 2px"——实测 mouse 一进卡片,屏幕位置就 dy = **-1.8px**。 我们自绘的宣纸卡不需要库那套悬停装饰,直接归零。
2px 的抖动正是用户说的「那个框会移动一下」。 ⚠️ 不去改 vendor 里那份文件(升级库会整份覆盖、改动静默消失):
我们的卡片是**自绘的宣纸卡**,不需要库那套"悬停上浮"装饰,所以直接归零。 在 app.css 覆盖即可 —— 它加载在 family-chart.css **之后**,
⚠️ 不能去改 vendor 里那份文件(升级库会整份覆盖,改动会静默消失), 同特异性下后写的赢,**不需要 !important**。 */
所以在 app.css 里覆盖;app.css 在 family-chart.css **之后**加载(见 kin/tree.php),
同特异性下后写的赢,不需要 !important。 */
.kin-chart.f3 div.card:hover > div { transform: none; } .kin-chart.f3 div.card:hover > div { transform: none; }
.kin-chart.f3 div.card > div { transition: none; } .kin-chart.f3 div.card > div { transition: none; }
.kin-card:hover { .kin-card:hover {
box-shadow: var(--shadow-l); border-color: var(--brand-2); box-shadow: var(--shadow-l); border-color: var(--brand-2);
} }
/* 头像外框:**长方形**,宽度贴合头像显示宽度。 /* 头像外框:**占满卡片内宽**(卡片 padding 横向为 0,所以这里 100% = 卡片内宽),
用户 2026-10-03 第 7 条:**头像本身是链接**(点它进这个人的资料页)。 长方形、object-fit: contain 不裁切。
⚠️ 它是 <a> 不是 <span> —— 见模板里的说明("点哪儿去哪儿"用链接最自然)。 */ 用户 2026-10-03 第 8 条:「外面那个框,宽度和头像图片一致,下面显示名字就行了」
—— 框宽 == 头像宽,头像下面只有名字。 */
.kin-card .kin-ph { .kin-card .kin-ph {
width: 96px; height: 72px; border-radius: 6px; overflow: hidden; width: 100%; height: 72px; border-radius: 8px 8px 0 0; overflow: hidden;
background: #F1E7D6; flex: 0 0 auto; background: #F1E7D6; flex: 0 0 auto;
display: block; display: block;
} }
.kin-card .kin-ph img { .kin-card .kin-ph img {
width: 100%; height: 100%; object-fit: contain; display: block; width: 100%; height: 100%; object-fit: contain; display: block;
} }
.kin-card .kin-ph:hover { box-shadow: 0 0 0 2px var(--brand-2); } .kin-card .kin-ph:hover { box-shadow: inset 0 0 0 2px var(--brand-2); }
/* 姓名:用户 2026-10-03 第 7 条改为**以他为中心看家谱**的入口(原来是进资料页)。 /* 姓名:用户 2026-10-03 第 7 条起,点姓名 = **以他为中心**重画家谱
底下一行小字说明这一处能干什么 —— 不然"点名字会换一张图"没人猜得到。 */ (点头像才是进资料页)。
⚠️ 第 8 条明确要求**不要再显示"资料 / 以他为中心"那两行悬停提示**(没必要)——
所以 .kin-hint 这套已整个删掉,卡片上头像下面只有名字。 */
.kin-card .kin-nm { .kin-card .kin-nm {
display: block; display: block;
font-size: 12.5px; font-weight: 600; line-height: 1.25; max-width: 100%; font-size: 12.5px; font-weight: 600; line-height: 1.25; max-width: 100%;
@@ -688,14 +702,6 @@ textarea.input { min-height: 76px; resize: vertical; }
margin-top: 5px; color: var(--ink); text-decoration: none; margin-top: 5px; color: var(--ink); text-decoration: none;
} }
.kin-card .kin-nm:hover { color: var(--brand); text-decoration: underline; } .kin-card .kin-nm:hover { color: var(--brand); text-decoration: underline; }
/* 两张"功能提示"用极小的字,且**只在指针悬停时**出现 ——
不悬停时不占视觉重量(卡片本来就小,常驻两行提示会把它塞满)。 */
.kin-card .kin-hint {
font-size: 9.5px; line-height: 1.2; color: var(--faint);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
max-width: 100%; opacity: 0; transition: opacity .15s ease;
}
.kin-card:hover .kin-hint { opacity: 1; }
/* 称呼在姓名下面一行,用品牌色的浅字(参考实现是「(称呼)」) */ /* 称呼在姓名下面一行,用品牌色的浅字(参考实现是「(称呼)」) */
.kin-card .kin-lb { .kin-card .kin-lb {
font-size: 10.5px; color: var(--brand); line-height: 1.2; max-width: 100%; font-size: 10.5px; color: var(--brand); line-height: 1.2; max-width: 100%;
+9 -5
View File
@@ -212,9 +212,15 @@ $datumJson = json_encode(
.setCardXSpacing(160) .setCardXSpacing(160)
.setCardYSpacing(180); .setCardYSpacing(180);
/* ⚠️ 卡片尺寸**必须与 CSS 里 .kin-card 的 --kin-w/--kin-h 一致**:
库按这个尺寸算卡片间距与连线落点,两边一旦不一致,连线就会与卡片错位
(用户 2026-10-03 反馈过一次"线又错位了",就是尺寸/居中改动引起的)。
98 = 96(头像宽)+ 左右各 1px 边框;见 app.css 的 .kin-card。 */
var KIN_W = 98, KIN_H = 116;
chart.setCardHtml() chart.setCardHtml()
.setStyle('rect') .setStyle('rect')
.setCardDim({ w: 118, h: 132, mini_tree: false }) .setCardDim({ w: KIN_W, h: KIN_H, mini_tree: false })
.setCardInnerHtmlCreator(function (d) { .setCardInnerHtmlCreator(function (d) {
var p = (d.data && d.data.data) || {}; var p = (d.data && d.data.data) || {};
var id = (d.data && d.data.id) || ''; var id = (d.data && d.data.id) || '';
@@ -243,10 +249,8 @@ $datumJson = json_encode(
+ esc(p.name) + '</a>' + esc(p.name) + '</a>'
+ '<span class="kin-lb">' + esc(p.label) + '</span>' + '<span class="kin-lb">' + esc(p.label) + '</span>'
+ (p.ph ? '<span class="kin-mark">待确认</span>' : '') + (p.ph ? '<span class="kin-mark">待确认</span>' : '')
// 两张提示分挂到头像/姓名下面,**悬停时才显形**(见 CSS 的 .kin-hint): // ⚠️ 用户 2026-10-03 第 8 条:**不要再放「资料 / 以他为中心」那两行悬停提示**
// 这样"点哪儿去哪儿"一眼可辨,不悬停时又不占地方。 // (原话「没必要,不要显示这个提示了」)。头像下面是名字,就够了。
+ '<span class="kin-hint kin-hint-ph">资料</span>'
+ '<span class="kin-hint kin-hint-nm">以他为中心</span>'
+ '</div>'; + '</div>';
}); });