/** * 底栏矢量线稿:给 canvas 按路径描边(沿图标轮廓,不是外接方框) * 点列在 24×24 视口里,绘制时按画布尺寸缩放 */ function pt(x, y) { return { x, y } } function dist(a, b) { return Math.hypot(b.x - a.x, b.y - a.y) } function pathLen(pts) { let len = 0 for (let i = 1; i < pts.length; i++) len += dist(pts[i - 1], pts[i]) return len } function withLen(pts) { return { pts, len: pathLen(pts) } } function linePts(a, b, n) { const pts = [a] for (let i = 1; i <= n; i++) { const t = i / n pts.push(pt(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)) } return pts } function cubicPts(p0, p1, p2, p3, n) { const pts = [p0] for (let i = 1; i <= n; i++) { const t = i / n const u = 1 - t pts.push( pt( u * u * u * p0.x + 3 * u * u * t * p1.x + 3 * u * t * t * p2.x + t * t * t * p3.x, u * u * u * p0.y + 3 * u * u * t * p1.y + 3 * u * t * t * p2.y + t * t * t * p3.y, ), ) } return pts } function arcPts(cx, cy, r, a0, a1, n) { const pts = [] for (let i = 0; i <= n; i++) { const a = a0 + (a1 - a0) * (i / n) pts.push(pt(cx + r * Math.cos(a), cy + r * Math.sin(a))) } return pts } function join(parts) { const pts = [] parts.forEach((seg, i) => { if (i === 0) pts.push(...seg) else pts.push(...seg.slice(1)) }) return pts } function roundRectPts(x, y, w, h, r) { return join([ linePts(pt(x + r, y), pt(x + w - r, y), 5), arcPts(x + w - r, y + r, r, -Math.PI / 2, 0, 4), linePts(pt(x + w, y + r), pt(x + w, y + h - r), 7), arcPts(x + w - r, y + h - r, r, 0, Math.PI / 2, 4), linePts(pt(x + w - r, y + h), pt(x + r, y + h), 5), arcPts(x + r, y + h - r, r, Math.PI / 2, Math.PI, 4), linePts(pt(x, y + h - r), pt(x, y + r), 7), arcPts(x + r, y + r, r, Math.PI, Math.PI * 1.5, 4), ]) } const p0 = pt(5.35, 19.7) const p1 = pt(12, 14.5) const p2 = pt(18.65, 19.7) export const TAB_ICON_STROKES = { home: [ withLen(join([linePts(pt(3.7, 10.95), pt(12, 4.05), 8), linePts(pt(12, 4.05), pt(20.3, 10.95), 8)])), withLen( join([ linePts(pt(6.45, 10.2), pt(6.45, 18.25), 8), cubicPts(pt(6.45, 18.25), pt(6.45, 19.08), pt(7.12, 19.75), pt(7.95, 19.75), 4), linePts(pt(7.95, 19.75), pt(10.1, 19.75), 3), linePts(pt(10.1, 19.75), pt(10.1, 15.4), 5), cubicPts(pt(10.1, 15.4), pt(10.1, 14.82), pt(10.57, 14.35), pt(11.15, 14.35), 3), linePts(pt(11.15, 14.35), pt(12.85, 14.35), 3), cubicPts(pt(12.85, 14.35), pt(13.43, 14.35), pt(13.9, 14.82), pt(13.9, 15.4), 3), linePts(pt(13.9, 15.4), pt(13.9, 19.75), 5), linePts(pt(13.9, 19.75), pt(16.05, 19.75), 3), cubicPts(pt(16.05, 19.75), pt(16.88, 19.75), pt(17.55, 19.08), pt(17.55, 18.25), 4), linePts(pt(17.55, 18.25), pt(17.55, 10.2), 8), ]), ), ], order: [ withLen(roundRectPts(6.35, 5.2, 11.3, 14.65, 1.85)), withLen(roundRectPts(9.15, 3.15, 5.7, 3.15, 0.85)), withLen(linePts(pt(9.35, 10.75), pt(14.65, 10.75), 5)), withLen(linePts(pt(9.35, 13.9), pt(14.65, 13.9), 5)), withLen(linePts(pt(9.35, 17.05), pt(12.7, 17.05), 4)), ], package: [ withLen(roundRectPts(4.2, 8.2, 15.6, 12.2, 1.6)), withLen(linePts(pt(4.2, 12.4), pt(19.8, 12.4), 6)), withLen(linePts(pt(12, 8.2), pt(12, 20.4), 6)), withLen(join([linePts(pt(8.2, 8.2), pt(12, 4.4), 4), linePts(pt(12, 4.4), pt(15.8, 8.2), 4)])), ], mine: [ withLen(arcPts(12, 8.05, 3.4, -Math.PI / 2, Math.PI * 1.5, 24)), withLen( join([ cubicPts(p0, pt(6.05, 16.4), pt(8.8, 14.5), p1, 8), cubicPts(p1, pt(15.2, 14.5), pt(17.95, 16.4), p2, 8), ]), ), ], } /** * 按进度 t(0~1) 沿图标轮廓描边 * 兼容微信旧 canvas(setStrokeStyle)和 2d(strokeStyle) */ export function paintTabIconStroke(ctx, key, t, color, size) { const paths = TAB_ICON_STROKES[key] if (!paths || !ctx) return const scale = size / 24 const width = 1.75 * scale if (typeof ctx.setStrokeStyle === 'function') { ctx.setStrokeStyle(color) ctx.setLineWidth(width) ctx.setLineCap('round') ctx.setLineJoin('round') } else { ctx.strokeStyle = color ctx.lineWidth = width ctx.lineCap = 'round' ctx.lineJoin = 'round' } const total = paths.reduce((sum, item) => sum + item.len, 0) let remain = total * Math.max(0, Math.min(1, t)) for (let p = 0; p < paths.length; p++) { if (remain <= 0) break const item = paths[p] const frac = Math.min(1, remain / (item.len || 1)) const pts = item.pts const count = Math.max(2, Math.ceil((pts.length - 1) * frac) + 1) ctx.beginPath() ctx.moveTo(pts[0].x * scale, pts[0].y * scale) for (let i = 1; i < count; i++) { ctx.lineTo(pts[i].x * scale, pts[i].y * scale) } ctx.stroke() remain -= item.len } }