上一批把显示区改成了「一组圆」,这一批让前台真正认多个圆。 - 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 个迷雾像素 ⇒ 第二个圆真的被认了,不是只看第一个。
37 lines
1.8 KiB
JavaScript
37 lines
1.8 KiB
JavaScript
/* 迷雾裁剪路径:把若干「容器像素圆」拼成一段 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);
|