重写选中便签的黑边为干净的外描边

This commit is contained in:
2026-09-19 14:13:25 +08:00
parent b94abe32fa
commit 86ee03f4a6
2 changed files with 50 additions and 51 deletions
+39 -37
View File
@@ -53,8 +53,9 @@ function sphericalAreaKm2(polys) {
} }
// 生成便签 canvas:黄底圆角 + 左侧箭头 + 黑色加粗文字(整张图,箭头尖端指向坐标) // 生成便签 canvas:黄底圆角 + 左侧箭头 + 黑色加粗文字(整张图,箭头尖端指向坐标)
// 选中态:先描粗边再填充,得到一条干净的 2px 外描边(四周预留 B 的 padding 防止裁切)
function makeStickyNote(text, fontSize, selected = false) { function makeStickyNote(text, fontSize, selected = false) {
const padX = 8, padY = 5, radius = 7, arrowW = 10, arrowH = 14, S = 2; // S: 2x 超采样 const padX = 8, padY = 5, radius = 7, arrowW = 10, arrowH = 14, S = 2, B = 2; // S: 2x 超采样,B: 边框预留
const font = `bold ${fontSize}px "Noto Serif SC", "Songti SC", "STSong", "SimSun", Georgia, serif`; const font = `bold ${fontSize}px "Noto Serif SC", "Songti SC", "STSong", "SimSun", Georgia, serif`;
const m = document.createElement('canvas'); const m = document.createElement('canvas');
const mctx = m.getContext('2d'); const mctx = m.getContext('2d');
@@ -63,55 +64,54 @@ function makeStickyNote(text, fontSize, selected = false) {
const bodyW = Math.max(1, Math.ceil(textW + padX * 2)); const bodyW = Math.max(1, Math.ceil(textW + padX * 2));
const w = arrowW + bodyW; const w = arrowW + bodyW;
const h = Math.ceil(fontSize + padY * 2); const h = Math.ceil(fontSize + padY * 2);
const cw = w + B * 2;
const ch = h + B * 2;
const c = document.createElement('canvas'); const c = document.createElement('canvas');
c.width = w * S; c.width = cw * S;
c.height = h * S; c.height = ch * S;
const ctx = c.getContext('2d'); const ctx = c.getContext('2d');
ctx.scale(S, S); ctx.scale(S, S);
// 阴影 const ox = B, oy = B; // 形状整体内缩 B,给黑边留空间
// 箭头 + 圆角矩形一体化路径(箭头尖端在 (ox, oy + h/2),指向坐标)
ctx.beginPath();
ctx.moveTo(ox, oy + h / 2);
ctx.lineTo(ox + arrowW, oy + h / 2 - arrowH / 2);
ctx.lineTo(ox + arrowW, oy + radius);
ctx.arcTo(ox + arrowW, oy, ox + arrowW + radius, oy, radius);
ctx.lineTo(ox + w - radius, oy);
ctx.arcTo(ox + w, oy, ox + w, oy + radius, radius);
ctx.lineTo(ox + w, oy + h - radius);
ctx.arcTo(ox + w, oy + h, ox + w - radius, oy + h, radius);
ctx.lineTo(ox + arrowW + radius, oy + h);
ctx.arcTo(ox + arrowW, oy + h, ox + arrowW, oy + h - radius, radius);
ctx.lineTo(ox + arrowW, oy + h / 2 + arrowH / 2);
ctx.closePath();
if (selected) {
// 先描粗边,再填充盖住内侧一半 → 外露 2px 干净黑边
ctx.strokeStyle = '#14100d';
ctx.lineWidth = B * 2; // 4px,外露 2px
ctx.lineJoin = 'round';
ctx.stroke();
} else {
// 非选中:柔和的投影
ctx.shadowColor = 'rgba(0, 0, 0, 0.20)'; ctx.shadowColor = 'rgba(0, 0, 0, 0.20)';
ctx.shadowBlur = 5; ctx.shadowBlur = 5;
ctx.shadowOffsetY = 2; ctx.shadowOffsetY = 2;
}
// 箭头 + 圆角矩形一体化路径(箭头尖端在左侧 x=0,指向坐标)
ctx.beginPath();
ctx.moveTo(0, h / 2); // 箭头尖端
ctx.lineTo(arrowW, h / 2 - arrowH / 2); // 箭头上沿
ctx.lineTo(arrowW, radius); // 左边缘上部
ctx.arcTo(arrowW, 0, arrowW + radius, 0, radius); // 左上圆角
ctx.lineTo(w - radius, 0); // 上边
ctx.arcTo(w, 0, w, radius, radius); // 右上圆角
ctx.lineTo(w, h - radius); // 右边
ctx.arcTo(w, h, w - radius, h, radius); // 右下圆角
ctx.lineTo(arrowW + radius, h); // 底边
ctx.arcTo(arrowW, h, arrowW, h - radius, radius); // 左下圆角
ctx.lineTo(arrowW, h / 2 + arrowH / 2); // 左边缘下部
ctx.closePath(); // 闭合回箭头尖端
ctx.fillStyle = '#e0c46a'; ctx.fillStyle = '#e0c46a';
ctx.fill(); ctx.fill();
// 选中时加黑边 // 黑色加粗文字
if (selected) {
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetY = 0;
ctx.strokeStyle = '#14100d';
ctx.lineWidth = 3;
ctx.stroke();
}
// 黑色加粗文字(无阴影)
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetY = 0;
ctx.fillStyle = '#14100d'; ctx.fillStyle = '#14100d';
ctx.font = font; ctx.font = font;
ctx.textBaseline = 'middle'; ctx.textBaseline = 'middle';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
ctx.fillText(text, arrowW + bodyW / 2, h / 2 + 1); ctx.fillText(text, ox + arrowW + bodyW / 2, oy + h / 2 + 1);
return { canvas: c, scale: 1 / S, width: w, height: h }; return { canvas: c, scale: 1 / S, width: cw, height: ch, tipX: B, tipY: B + h / 2 };
} }
export class CesiumGlobe { export class CesiumGlobe {
@@ -300,6 +300,8 @@ export class CesiumGlobe {
el.style.height = `${m.noteNormal.height}px`; el.style.height = `${m.noteNormal.height}px`;
el.dataset.index = m.index; el.dataset.index = m.index;
m.el = el; m.el = el;
m.tipX = m.noteNormal.tipX; // 箭头尖相对元素左上角的偏移
m.tipY = m.noteNormal.tipY;
m.normalUrl = m.noteNormal.canvas.toDataURL(); m.normalUrl = m.noteNormal.canvas.toDataURL();
m.selectedUrl = m.noteSelected.canvas.toDataURL(); m.selectedUrl = m.noteSelected.canvas.toDataURL();
el.style.backgroundImage = `url("${m.normalUrl}")`; el.style.backgroundImage = `url("${m.normalUrl}")`;
@@ -335,8 +337,8 @@ export class CesiumGlobe {
el.style.display = 'block'; el.style.display = 'block';
el.style.opacity = String(opacity); el.style.opacity = String(opacity);
el.style.left = `${win.x}px`; el.style.left = `${win.x - m.tipX}px`;
el.style.top = `${win.y}px`; el.style.top = `${win.y - m.tipY}px`;
} }
} }
+10 -13
View File
@@ -47,7 +47,6 @@ html, body {
pointer-events: auto; pointer-events: auto;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
transform: translateY(-50%);
cursor: pointer; cursor: pointer;
} }
@@ -66,27 +65,25 @@ html, body {
background: #0c0906; opacity: 0; transition: opacity 0.18s ease; background: #0c0906; opacity: 0; transition: opacity 0.18s ease;
} }
/* 加载界面 */ /* 加载界面:OS1 3D 加载环 */
#loading { #loading {
position: fixed; inset: 0; z-index: 1000; position: fixed; inset: 0; z-index: 1000;
display: flex; align-items: center; justify-content: center; display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 26px;
background: #14100d; background: #14100d;
transition: opacity 0.6s ease; transition: opacity 0.6s ease;
} }
#loading.hidden { opacity: 0; pointer-events: none; } #loading.hidden { opacity: 0; pointer-events: none; }
.loading-box { display: flex; flex-direction: column; align-items: center; gap: 18px; } #loading-canvas {
.spinner { width: 500px; height: 500px;
width: 54px; height: 54px; max-width: 88vw; max-height: 88vw;
border: 3px solid rgba(201, 162, 39, 0.22);
border-top-color: #c9a227;
border-radius: 50%;
animation: spin 0.9s linear infinite;
} }
@keyframes spin { to { transform: rotate(360deg); } } #loading-canvas canvas { width: 100%; height: 100%; display: block; }
.loading-text { .loading-text {
font-family: var(--serif); font-family: var(--serif);
font-size: 14px; letter-spacing: 0.3em; font-size: 13px;
color: rgba(230, 216, 182, 0.7); letter-spacing: 0.2em;
color: rgba(224, 196, 106, 0.8);
} }
/* 顶部标题 */ /* 顶部标题 */