import { groupChat } from './group-chat/Index.ts';

/**
 * 彩蛋模块。
 * 处理版本号点击触发的破碎效果和群聊彩蛋。
 */
export class EasterEgg {
    private _clickCount: number = 0;
    private _lastClickTime: number = 0;
    private readonly _versionElement: HTMLElement | null;
    private _audioCtx: AudioContext | null = null;
    private _restoreTimer: ReturnType<typeof setTimeout> | null = null;

    public constructor() {
        this._versionElement = document.getElementById('app-version');

        if (this._versionElement) {
            this.init();
        }
    }

    /**
     * 初始化彩蛋功能。
     */
    private init(): void {
        if (!this._versionElement) {
            return;
        }

        this._versionElement.addEventListener('click', (e: MouseEvent) => {
            this.handleClick(e);
        });

        this._versionElement.style.cursor = 'pointer';
        this._versionElement.style.userSelect = 'none';
        this._versionElement.title = '版本号';
    }

    /**
     * 处理版本号点击事件。
     */
    private handleClick(e: MouseEvent): void {
        const now = Date.now();

        /*
         * 如果距离上次点击超过 2 秒,则重置计数器
         */
        if (now - this._lastClickTime > 2000) {
            this._clickCount = 0;
        }

        this._lastClickTime = now;
        this._clickCount++;

        this.animateClick();

        if (this._clickCount === 5) {
            this.triggerShakeEffect();
        } else if (this._clickCount > 5 && this._clickCount < 10) {
            /*
             * 如果在破碎状态下继续点击,则延长自动恢复时间
             */
            if (document.body.classList.contains('easter-egg--broken')) {
                this.resetAutoRestoreTimer();
            }
        } else if (this._clickCount >= 10) {
            this.triggerGroupChat();
        }
    }

    /**
     * 播放点击动画效果。
     */
    private animateClick(): void {
        if (!this._versionElement) {
            return;
        }

        this._versionElement.style.transition = 'transform 0.1s';
        this._versionElement.style.transform = 'scale(0.9)';

        window.setTimeout(() => {
            if (this._versionElement) {
                this._versionElement.style.transform = 'scale(1)';
            }
        }, 100);
    }

    /**
     * 触发震动破碎效果。
     */
    private triggerShakeEffect(): void {
        this.resetEffects();

        /*
         * 创建石头元素(动画由 CSS 控制)
         */
        const stone = document.createElement('div');
        stone.classList.add('easter-egg-stone');
        stone.innerText = '🪨';
        document.body.appendChild(stone);

        /*
         * 0.6 秒后石头飞行完毕,触发碰撞效果
         */
        window.setTimeout(() => {
            stone.remove();

            this.createImpactFlash();
            this.playBreakSound();

            document.body.classList.add('easter-egg--shake');
            document.body.classList.add('easter-egg--broken');

            /*
             * 500 毫秒后停止震动
             */
            window.setTimeout(() => {
                document.body.classList.remove('easter-egg--shake');
            }, 500);

            this.resetAutoRestoreTimer();
        }, 600);
    }

    /**
     * 创建撞击闪白效果。
     */
    private createImpactFlash(): void {
        const flash = document.createElement('div');
        flash.classList.add('easter-egg-flash');
        document.body.appendChild(flash);

        /*
         * 动画结束后移除闪白层
         */
        flash.addEventListener('animationend', () => {
            flash.remove();
        });
    }

    /**
     * 重置自动恢复定时器。
     */
    private resetAutoRestoreTimer(): void {
        if (this._restoreTimer) {
            clearTimeout(this._restoreTimer);
        }

        /*
         * 随机 5-10 秒后自动恢复
         */
        const delay = 5000 + Math.random() * 5000;
        this._restoreTimer = window.setTimeout(() => {
            this.resetEffects();
        }, delay);
    }

    /**
     * 重置所有效果。
     */
    private resetEffects(): void {
        document.body.classList.remove('easter-egg--shake');
        document.body.classList.remove('easter-egg--broken');

        if (this._restoreTimer) {
            clearTimeout(this._restoreTimer);
        }
    }

    /**
     * 触发群聊彩蛋。
     */
    private triggerGroupChat(): void {
        this.resetEffects();

        groupChat.open();

        this._clickCount = 0;
    }

    /**
     * 播放玻璃破碎音效。
     * 使用 Web Audio API 合成破碎音效。
     */
    private playBreakSound(): void {
        try {
            if (!this._audioCtx) {
                const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
                this._audioCtx = new AudioContextClass();
            }

            /*
             * 如果音频上下文处于挂起状态(浏览器策略),尝试恢复
             */
            if (this._audioCtx.state === 'suspended') {
                void this._audioCtx.resume();
            }

            const ctx = this._audioCtx;

            /*
             * 创建白噪音缓冲区(2 秒)
             */
            const bufferSize = ctx.sampleRate * 2.0;
            const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
            const data = buffer.getChannelData(0);

            for (let i = 0; i < bufferSize; i++) {
                data[i] = Math.random() * 2 - 1;
            }

            /*
             * 创建噪音源
             */
            const noise = ctx.createBufferSource();
            noise.buffer = buffer;

            /*
             * 高通滤波模拟玻璃的高频声
             */
            const filter = ctx.createBiquadFilter();
            filter.type = 'highpass';
            filter.frequency.value = 800;
            filter.Q.value = 10;

            /*
             * 包络:控制音量变化模拟撞击和余音
             */
            const gain = ctx.createGain();

            noise.connect(filter);
            filter.connect(gain);
            gain.connect(ctx.destination);

            const now = ctx.currentTime;

            /*
             * 冲击:瞬间大音量
             */
            gain.gain.setValueAtTime(0, now);
            gain.gain.linearRampToValueAtTime(0.8, now + 0.01);

            /*
             * 衰减:迅速下降
             */
            gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);

            noise.start(now);
            noise.stop(now + 0.3);
        } catch (e) {
            console.warn('Easter egg sound synthesis failed:', e);
        }
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个非常有趣的彩蛋模块,实现了一个交互式的版本号点击效果。让我详细解释一下这个代码的功能和实现:

主要功能

这个模块监听页面中版本号元素的点击事件,根据不同的点击次数触发不同的彩蛋效果:

1. 点击计数机制

private handleClick(e: MouseEvent): void {
    const now = Date.now();
    
    // 如果距离上次点击超过 2 秒,则重置计数器
    if (now - this._lastClickTime > 2000) {
        this._clickCount = 0;
    }
    
    this._lastClickTime = now;
    this._clickCount++;
    // ...
}
  • 在2秒内的连续点击才会累计计数
  • 超过2秒间隔会重置计数器

2. 不同阶段的效果

阶段1:基础点击动画(每次点击)

private animateClick(): void {
    this._versionElement.style.transform = 'scale(0.9)';
    // 100ms后恢复原始大小
}
  • 每次点击都有一个缩放动画效果

阶段2:破碎效果(第5次点击)

private triggerShakeEffect(): void {
    // 1. 创建石头元素飞行动画
    const stone = document.createElement('div');
    stone.classList.add('easter-egg-stone');
    stone.innerText = '🪨';
    
    // 2. 600ms后触发撞击效果
    setTimeout(() => {
        this.createImpactFlash();     // 闪白效果
        this.playBreakSound();        // 破碎音效
        document.body.classList.add('easter-egg--shake');   // 震动
        document.body.classList.add('easter-egg--broken');  // 破碎状态
    }, 600);
}

阶段3:群聊彩蛋(第10次点击)

private triggerGroupChat(): void {
    this.resetEffects();
    groupChat.open();  // 打开群聊功能
    this._clickCount = 0;
}

3. 音效合成

最有趣的部分是使用 Web Audio API 实时合成玻璃破碎音效:

private playBreakSound(): void {
    // 创建白噪音
    const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < bufferSize; i++) {
        data[i] = Math.random() * 2 - 1;  // 生成随机噪音
    }
    
    // 高通滤波模拟玻璃高频声
    const filter = ctx.createBiquadFilter();
    filter.type = 'highpass';
    filter.frequency.value = 800;
    
    // 音量包络:冲击 → 衰减
    gain.gain.setValueAtTime(0, now);
    gain.gain.linearRampToValueAtTime(0.8, now + 0.01);    // 瞬间冲击
    gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3); // 快速衰减
}

4. 自动恢复机制

private resetAutoRestoreTimer(): void {
    // 随机 5-10 秒后自动恢复正常状态
    const delay = 5000 + Math.random() * 5000;
    this._restoreTimer = setTimeout(() => {
        this.resetEffects();
    }, delay);
}

设计亮点

  1. 渐进式交互:从简单点击动画到复杂的破碎效果,再到隐藏功能
  2. 视听结合:视觉效果(震动、闪白、破碎)+ 音效合成
  3. 用户体验:自动恢复机制避免永久破坏界面
  4. 技术展示:Web Audio API 的创意使用,实时合成音效而非播放预录音频

这是一个很好的用户体验设计案例,既有趣又不会干扰正常使用。

评论加载中...