fix(timer): 分段弧光点居中置顶 + 拉大与进度弧间距

- 光点移到段中点(am=(a0+a1)/2),两端粗细对称;重构为两遍绘制,
  光点统一后绘浮于所有弧之上,不再被相邻圆头吞掉;加大加亮成珠状
- glowPad 6→18 与 _drawSetArcs pad 同步,arcR 外移 3→14,
  分段弧与进度弧间距 -1 → +10dpr(size=420),呼吸光环不重叠
This commit is contained in:
2026-08-12 10:34:54 +08:00
parent f1ad3eeb95
commit b6d94b21cd
2 changed files with 58 additions and 23 deletions
+17
View File
@@ -89,3 +89,20 @@
- **`timer.wxss`**:新增 `.completion-not-recorded` 主色淡底信息块(与 completion-tip 同风格,tipIn 延迟 0.5s 入场)。 - **`timer.wxss`**:新增 `.completion-not-recorded` 主色淡底信息块(与 completion-tip 同风格,tipIn 延迟 0.5s 入场)。
- 保留逻辑:`elapsed < 3` 秒仍走 toast「训练时间太短」+ navigateBack(几乎未开始,不弹完成弹窗)。 - 保留逻辑:`elapsed < 3` 秒仍走 toast「训练时间太短」+ navigateBack(几乎未开始,不弹完成弹窗)。
- 验证:node --check 通过;grep 确认 minRecordSeconds/completionNotRecorded/_showNotRecorded 三处字段在 config/js/wxml 引用一致。 - 验证:node --check 通过;grep 确认 minRecordSeconds/completionNotRecorded/_showNotRecorded 三处字段在 config/js/wxml 引用一致。
## 分段弧两端粗细不一致(已修)
- 用户反馈:循环训练时分段弧"两端粗细不一致"。
- 根因(progress-ring.js `_drawSetArcs`):发光光点坐标固定取段尾角 `a1``dx=cx+arcR*cos(a1)`),当前段(active)/休息预告段(next)的尾端 = 光点+8dpr 发光晕(直径≈弧宽+),首端却只是 `lineCap:'round'` 普通圆头 → 同段弧尾粗头细。已完成/未开始段两端都是普通圆头,粗细一致,所以现象集中在运动中那一段。
- 修复:光点移到**段中点** `am=(a0+a1)/2`(3 行),两端自然对称,光点仍作"当前组"标记。node --check 通过。
- 注意:若以后想给分段弧加"当前组跟随倒计时移动"的进度感,光点可改为随剩余时间在段内插值,但需先确认用户是否要这个效果。
## 分段弧间距 + 光点置顶(用户反馈"两弧太近、弧压着光点")
- 用户反馈两点:①分段弧与进度弧距离太近;②光点被分段弧压住(视觉上弧盖着光点)。
- **间距修复**progress-ring.jssize=420 时验证):
- 根因:间距 = gap arcW/2 = 34 = **−1dpr(重叠 1)**。因为分段弧 arcR 仅比进度弧外缘靠外 3dpr,而弧半宽 4dpr。
- `_draw()``glowPad` 6→14dpr(进度弧半径内缩 8,给外圈腾空间);`_drawSetArcs()` 的 radius pad 6→14**两处必须同步**,已加注释)且 arcR 间距 `+3→+9dpr`。结果:进度弧外缘 204→196、分段弧内缘 203→201,间距 −1→**+5dpr**。副作用:单组/自由模式的进度环也内缩 ~8dpr(约 4%),中心数字区略小;进度弧尾端发光点 shadowBlur=12 阴影反而从"被 canvas 裁"变完整。
- 段间空隙 gap=2.5° 是段间距,**与环间距无关,不动**。
- **光点置顶**:原实现光点在逐段循环内、每段弧 stroke 之后画,理论上应在弧上,但视觉上被 round cap 相邻段边缘"吞掉"。重构为**两遍绘制**:pass1 只画所有弧并收集 emphasized 段光点(x/y/color),pass2 统一在所有弧之上画光点。同时光点加大加亮:dotR 从 `0.85~1.2×弧半宽``1.15~1.4×`alpha `0.55~1.0``0.7~1.0`,白芯 0.85→0.9,读起来像"珠珠骑在弧上"。
- 光点仍居段中点(沿用上轮两端对称修复);外缘 209dpr 不超 canvas 半宽 210,与 breath-aura(内缘 ~232)无重叠。
- 验证:node --check 通过;数值全部核算。未提交 git。
- **间距仍不够(用户反馈"还是太近")→ 再翻倍**canvas 半宽 210 是硬上限,剩余空间仅 ~5dpr,要明显拉开只能继续内缩进度弧。glowPad 14→18、arcR 外移量 9→14,间距 **5→10dpr**(进度弧外缘 196→192,分段弧内缘 201→202,光点外缘 211.6 超画布 0.8px 亚像素无感)。进度环直径累计缩 ~6%(408→384)。约束备忘:**间距 = arcR外移量 arcW/2,且 ≤ glowPad arcW margin**;想再拉开只能继续缩环或减弧宽。
+41 -23
View File
@@ -105,8 +105,11 @@ Component({
const cx = w / 2 const cx = w / 2
const cy = h / 2 const cy = h / 2
// reserve room for the glowing end dot so its shadow isn't clipped // Reserve room for the glowing end dot's shadow AND the outer set-progress
const glowPad = 6 * dpr // band (circuit mode). _drawSetArcs derives its band radius from this same
// pad, so increasing it here also widens the gap between the countdown
// arc and the set band. 18dpr gives a ~10dpr visible gap (size=420).
const glowPad = 18 * dpr
const radius = (size - ringWidth) / 2 * dpr - glowPad const radius = (size - ringWidth) / 2 * dpr - glowPad
const lw = ringWidth * dpr const lw = ringWidth * dpr
@@ -252,8 +255,9 @@ Component({
const cy = (size / 2) * dpr const cy = (size / 2) * dpr
// Same radius math as _draw(): the countdown arc sits at `radius`; the // Same radius math as _draw(): the countdown arc sits at `radius`; the
// set band wraps just outside it so the two read as concentric layers. // set band wraps just outside it so the two read as concentric layers.
const radius = ((size - ringWidth) / 2) * dpr - 6 * dpr // NOTE: the -18*dpr pad MUST stay in sync with glowPad in _draw().
const arcR = radius + (ringWidth / 2) * dpr + 3 * dpr const radius = ((size - ringWidth) / 2) * dpr - 18 * dpr
const arcR = radius + (ringWidth / 2) * dpr + 14 * dpr
const arcW = 8 * dpr const arcW = 8 * dpr
const band = arcW / 2 + 3 * dpr const band = arcW / 2 + 3 * dpr
@@ -274,6 +278,10 @@ Component({
const gap = (2.5 * Math.PI) / 180 const gap = (2.5 * Math.PI) / 180
const startAngle = -Math.PI / 2 const startAngle = -Math.PI / 2
// Pass 1: draw every segment arc. Breathing dots are collected and painted
// in pass 2, AFTER all arcs, so a dot always floats ON TOP of its segment
// instead of being partially buried under neighbouring round caps.
const dots = []
for (let i = 1; i <= sets; i++) { for (let i = 1; i <= sets; i++) {
const a0 = startAngle + (i - 1) * segAngle + gap / 2 const a0 = startAngle + (i - 1) * segAngle + gap / 2
const a1 = startAngle + i * segAngle - gap / 2 const a1 = startAngle + i * segAngle - gap / 2
@@ -294,28 +302,38 @@ Component({
ctx.stroke() ctx.stroke()
ctx.globalAlpha = 1 ctx.globalAlpha = 1
// Glowing endpoint dot on the emphasized segment.
if (emphasized) { if (emphasized) {
const dx = cx + arcR * Math.cos(a1) const am = (a0 + a1) / 2
const dy = cy + arcR * Math.sin(a1) dots.push({
const dotR = (arcW / 2) * (paused ? 1 : 0.85 + 0.35 * p) x: cx + arcR * Math.cos(am),
const glowColor = i === active ? primaryColor : successColor y: cy + arcR * Math.sin(am),
ctx.save() color: i === active ? primaryColor : successColor
ctx.shadowColor = glowColor })
ctx.shadowBlur = 8 * dpr
ctx.globalAlpha = paused ? 0.9 : 0.55 + 0.45 * p
ctx.beginPath()
ctx.arc(dx, dy, dotR, 0, Math.PI * 2)
ctx.fillStyle = glowColor
ctx.fill()
ctx.restore()
// white core for a "light bulb" feel
ctx.beginPath()
ctx.arc(dx, dy, dotR * 0.45, 0, Math.PI * 2)
ctx.fillStyle = 'rgba(255,255,255,0.85)'
ctx.fill()
} }
} }
// Pass 2: glowing dot on each emphasized segment, drawn over every arc.
// Placed at the segment MIDDLE (not the a1 end): a dot pinned to the
// trailing end made that end look visibly thicker than the round-cap
// start, so both ends of the arc stay symmetric. Slightly larger than
// the band width so it reads as a bead riding on top of the arc.
for (const dot of dots) {
const dotR = (arcW / 2) * (paused ? 1.3 : 1.15 + 0.25 * p)
ctx.save()
ctx.shadowColor = dot.color
ctx.shadowBlur = 8 * dpr
ctx.globalAlpha = paused ? 0.95 : 0.7 + 0.3 * p
ctx.beginPath()
ctx.arc(dot.x, dot.y, dotR, 0, Math.PI * 2)
ctx.fillStyle = dot.color
ctx.fill()
ctx.restore()
// white core for a "light bulb" feel
ctx.beginPath()
ctx.arc(dot.x, dot.y, dotR * 0.45, 0, Math.PI * 2)
ctx.fillStyle = 'rgba(255,255,255,0.9)'
ctx.fill()
}
}, },
_startSetPulse() { _startSetPulse() {