Files

96 lines
4.4 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.
// 登录页流星轨迹验证
// 目的:确认「流星进入视口期间始终不透明」,且「透明度开始变化时整条流星已在视口外」
// 用法: node _design-test/verify-meteor.js [视口宽] [视口高] [试验次数]
const VW = Number(process.argv[2] || 1280)
const VH = Number(process.argv[3] || 720)
const N = Number(process.argv[4] || 20000)
// 动画时间轴(与 login.vue 的 @keyframes meteor-shower 一致)
// 0% 透明度 0 → 0.5% 变 1 → 60% 仍为 1 → 66.7% 变 0 → 100% 保持 0
// 位移在 0%~60% 内从起点线性走到终点,60% 之后停在终点
const FLY_END = 0.60 // 飞行结束(到达终点)的进度
const FADE_IN_END = 0.005 // 淡入完成的进度(0.5%)
const FADE_OUT_START = 0.60 // 淡出开始的进度
const inside = (x, y) => x >= 0 && x <= VW && y >= 0 && y <= VH
// 判断「头部 + 拖尾」这条线段是否与视口相交(沿线采样)
function streakVisible(hx, hy, ux, uy, tail) {
for (let i = 0; i <= 40; i++) {
const s = (i / 40) * tail
if (inside(hx - ux * s, hy - uy * s)) return true
}
return false
}
function headAt(c) { return Math.min(c / FLY_END, 1) } // 进度 -> 路径参数
function build(variant) {
const tail = 90 + Math.random() * 70
const margin = tail + 60
const theta = (25 + Math.random() * 30) * Math.PI / 180
const ux = -Math.cos(theta), uy = Math.sin(theta)
if (variant === 'old') {
// 旧实现:起终点都取在视口内部
const sx = VW * (0.50 + Math.random() * 0.50)
const sy = VH * (Math.random() * 0.35)
const ex = VW * (Math.random() * 0.30)
const ey = VH * (0.85 + Math.random() * 0.20)
return { sx, sy, tx: ex - sx, ty: ey - sy, ux: (sx - ex), uy: (sy - ey), tail, margin }
}
// 新实现
let sx, sy
if (Math.random() < 0.5) { sx = VW * (0.45 + Math.random() * 0.65); sy = -margin }
else { sx = VW + margin; sy = VH * (0.10 + Math.random() * 0.40) }
const t = Math.min((VH + margin - sy) / uy, (-margin - sx) / ux)
return { sx, sy, tx: ux * t, ty: uy * t, ux, uy, tail, margin }
}
function analyze(variant) {
let worstFadeWhileVisible = 0 // 可见期间透明度掉到 1 以下的最大程度(0 = 完美)
let maxVisibleAtFlyEnd = 0 // 飞行结束时仍可见的比例
let minEnter = 1, maxEnter = 0, minExit = 1, maxExit = 0
const visAt = []
for (let n = 0; n < N; n++) {
const m = build(variant)
const ulen = Math.hypot(m.ux, m.uy) || 1
const ux = m.ux / ulen, uy = m.uy / ulen // 单位化的「拖尾指向」
let enter = -1, exit = -1
for (let i = 0; i <= 600; i++) {
const c = i / 600
const f = headAt(c)
const hx = m.sx + m.tx * f, hy = m.sy + m.ty * f
const vis = streakVisible(hx, hy, ux, uy, m.tail)
if (vis && enter < 0) enter = c
if (vis) exit = c
}
if (enter < 0) continue
visAt.push(enter)
if (enter < minEnter) minEnter = enter
if (enter > maxEnter) maxEnter = enter
if (exit < minExit) minExit = exit
if (exit > maxExit) maxExit = exit
// 可见窗口内透明度是否恒为 1
if (enter < FADE_IN_END) worstFadeWhileVisible = Math.max(worstFadeWhileVisible, FADE_IN_END - enter)
if (exit > FADE_OUT_START) worstFadeWhileVisible = Math.max(worstFadeWhileVisible, exit - FADE_OUT_START)
maxVisibleAtFlyEnd = Math.max(maxVisibleAtFlyEnd, Math.max(0, exit - FADE_OUT_START))
}
visAt.sort((a, b) => a - b)
const med = visAt[Math.floor(visAt.length / 2)] || 0
return { minEnter, maxEnter, medEnter: med, minExit, maxExit, worstFadeWhileVisible, maxVisibleAtFlyEnd, count: visAt.length }
}
for (const v of ['old', 'new']) {
const r = analyze(v)
const f1 = n => (n * 100).toFixed(1)
console.log((v === 'old' ? '【旧】' : '【新】') + ' 实现 视口 ' + VW + 'x' + VH + ' 有效样本 ' + r.count)
console.log(' 可见区间:进入 ' + f1(r.minEnter) + '%~' + f1(r.maxEnter) + '% 离开 ' +
f1(r.minExit) + '%~' + f1(r.maxExit) + '%')
console.log(' 可见期间透明度不足的最大窗口:' + f1(r.worstFadeWhileVisible) + '% of 周期' +
(r.worstFadeWhileVisible <= 0.0001 ? ' ✓ 无(可见时恒为不透明)' : ' ← 会在屏内淡入/淡出'))
console.log(' 飞行结束时仍未出屏的量:' + f1(r.maxVisibleAtFlyEnd) + '%' +
(r.maxVisibleAtFlyEnd <= 0.0001 ? ' ✓ 整条流星已出屏' : ' ← 未出屏就已淡出'))
console.log('')
}