Files
lgp-wx-plus/utils/tabbar-icons.js
2026-08-19 08:17:18 +08:00

163 lines
4.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.
/**
* 底栏矢量线稿:给 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
}
}