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