Files
tamakyi 44763dfc3f 专题的多圆显示区:前台按并集裁剪迷雾
上一批把显示区改成了「一组圆」,这一批让前台真正认多个圆。

- clip-path 从 circle() 换成 path():circle() 只表达得了一个圆。每个圆一段
  圆弧子路径、**同绕向**(sweep=0),nonzero 填充规则下多段自然取并集,
  重叠处不会互相挖洞。
- 拼路径的活单独放 fog-clip.js(rond-map.js 是个大 IIFE,函数取不出来),
  这样能直接跑 node 单测;layout 里必须排在 rond-map.js 之前加载。
- 顺带清掉已经死掉的 fitCircle:多圆之后「圆心 + 半径」表达不了,留着就是
  两套并行的贴合口径。fitParams 现在只给 fitBBox(单圆时外接矩形正是那个
  圆的外接正方形,视野等价)。
- 守卫改名 TestTopicFogIsClippedToAreas,钉住两端(模板发 fogAreas ↔ 脚本认
  fogAreas)与两处顺序(裁剪注册在 applyFit 之后、fog-clip.js 在 rond-map.js
  之前);fogAreas 必须**无条件**下发(空数组 = 不限区域),不要包在条件里。
- verify.sh 挂进前端单测:以前只靠人记得手动跑 = 等于没有。找不到 node 直接
  失败而不是跳过(跳过会让验收看着全绿、实际一行前端脚本都没验)。

验证(临时两圆专题,SQL 造的,已删):
- fog-clip.test.js 15 条:把生成的 path 解析回圆弧再验几何,含绕向一致性与
  半径 0/负数的夹取。
- 端到端像素验证:自校准出「CSS 容器坐标 → 图像像素」(scale≈2、offset≈19.2,
  正是 site_cdp 的 --clip 外扩 10px×scale,别再手算);三张掩码图证明并集语义
  —— A 独有点只有 A 亮、B 独有点只有 B 亮、**重叠中心两圆都亮且并集也亮
  (无洞)**,整体集合差异 0.013%。
- 迷雾掩码:86,776 个像素,按两圆并集的解析式判 **圆外 0 个**;两个圆内分别
  有 52,609 / 46,757 个迷雾像素 ⇒ 第二个圆真的被认了,不是只看第一个。
2026-10-12 16:44:55 +08:00

37 lines
1.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 迷雾裁剪路径:把若干「容器像素圆」拼成一段 clip-path: path() 数据。
*
* 为什么自己拼 path():clip-path: circle() 只表达得了一个圆,而专题的显示区是一组圆。
* 每个圆一段子路径、圆弧一律 sweep=0(**同绕向**)—— nonzero 填充规则下多段自然取并集,
* 重叠处不会互相挖洞;如果两段绕向相反,重叠区会被抵消成一个洞。
* 用两段半圆弧拼整圆,而不是往 DOM 里插 <circle>,是为了整串塞进一个 path(),
* 不用额外维护 SVG 节点与它的坐标系。
*
* 单独一个文件(而不是塞进 rond-map.js)是为了能直接跑 node 单测:
* rond-map.js 是个大 IIFE,函数取不出来。
*/
(function (global) {
'use strict';
// circles: [{x, y, r}],单位都是**地图容器像素**(与 clip-path 的坐标系一致)。
// 返回可直接赋给 style.clipPath 的字符串;没有圆时返回空串(调用方据此不改样式)。
function pathFor(circles) {
if (!circles || !circles.length) return '';
var d = '';
for (var i = 0; i < circles.length; i++) {
var c = circles[i];
var x = c.x, y = c.y;
// 半径下限 1px:缩到极小时 r=0 会让路径退化(浏览器可能整段忽略)
var r = Math.max(1, c.r);
var rr = r.toFixed(1), w = (2 * r).toFixed(1);
d += 'M' + (x - r).toFixed(1) + ' ' + y.toFixed(1) +
'a' + rr + ' ' + rr + ' 0 1 0 ' + w + ' 0' +
'a' + rr + ' ' + rr + ' 0 1 0 ' + (-2 * r).toFixed(1) + ' 0Z';
}
return 'path("' + d + '")';
}
var api = { pathFor: pathFor };
if (typeof module !== 'undefined' && module.exports) module.exports = api;
global.rondFogClip = api;
})(typeof window !== 'undefined' ? window : globalThis);