从“灵魂拷问”到“时间胶囊”:一个前端小白与Trae的深夜奇遇记 ⏱️

192 阅读7分钟

前言:当人类开始和AI吵架

某天深夜,当我对着IDE疯狂敲击键盘时,突然灵光乍现:"要是能写个程序,把现在的烦恼寄给未来的自己就好了!"
(此时Trae突然从代码编辑器里探出头)
Trae: "您这是想给自己造个数字版时空快递员吗?"
我: "对啊对啊!但是我的CSS连盒子模型都搞不定..."
Trae: "(推了推不存在的黑框眼镜)建议从localStorage开始,再用crypto-js给数据穿层量子护甲。"

微信图片_20250608183851.png

功能亮点:不只是"Hello World"的升级版

image.png

  • 时空快递员模式:支持文字/图片/音频三重时空包裹投递
  • 量子加密保险箱:用crypto-js给记忆穿三层纳米防护衣
  • 时间炸弹预警系统:倒计时精确到毫秒,CSS粒子爆破特效堪比烟花秀
  • 未来时空漫游指南:到达指定日期自动触发《星际迷航》式全息投影

技术实现:当代程序员的炼金术

image.png

1. 时空定位引擎(localStorage+crypto-js)

// 加密密钥:比星巴克密码安全指数高3个数量级
const SECRET_KEY = 'time-capsule-secret-key';

// 量子纠缠加密函数
function encryptData(data) {
    return CryptoJS.AES.encrypt(JSON.stringify(data), SECRET_KEY).toString();
}

2. 时间坐标系(倒计时计算)

// 浏览器时间扭曲算法
function loadCapsules() {
    const capsules = JSON.parse(localStorage.getItem('capsules') || '[]');
    
    capsules.forEach(encryptedCapsule => {
        const capsule = decryptData(encryptedCapsule);
        const timeLeft = new Date(capsule.openDate) - new Date();
        // 当前宇宙与目标宇宙的时间差转换为"天"单位
        const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24));
    });
}

3. 超维动画系统(CSS粒子爆破)

.particle {
    position: absolute;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    animation: explode 0.5s ease-out forwards;
}

@keyframes explode {
    0% { transform: scale(1); opacity: 1; }
    100% { transform: scale(3); opacity: 0; }
}

主要对话:Trae发挥神力

image.png

我:"创意时间胶囊 功能亮点: 用户输入文字、图片或音频(录音功能),设定未来某天打开。 数据加密存储(crypto-js 加密后存入 localStorage)。 到达指定日期后,触发惊喜动画(CSS 粒子爆破、音效)。 技术实现: JS 计算当前时间与目标时间差,动态倒计时。 CSS 动画模拟“胶囊打开”效果。 示例:用户写下“2025年7月1日给未来的自己”,到那天打开时播放预设的语音和文字。"

我:"CSS样式可以帮我改的更加美观,淡蓝色主题"

开发历程:一场与浏览器的拉锯战

image.png

Trae: "建议用MediaRecorder API实现语音捕获"
我: "那玩意会不会吃内存啊?"
Trae: "放心,它只会吃你设置的音频类型,比如audio/wav"
我: "那用户权限怎么处理?"
Trae: "(甩出代码)试试这个优雅的Promise链:navigator.mediaDevices.getUserMedia({ audio: true })"

源码大赏:当代程序员的《清明上河图》

image.png

完整源码如下(已通过量子计算机验证无bug):

index.html

<!-- 时空胶囊主控面板 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>创意时间胶囊</title>
    <link rel="stylesheet" href="styles.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
</head>
<body>
    <div class="container">
        <h1>创意时间胶囊⏱️</h1>
        
        <div class="capsule-form">
            <div class="input-group">
                <label for="message">写下你想对未来的自己说的话:</label>
                <textarea id="message" rows="4" placeholder="在这里输入你的消息..."></textarea>
            </div>

            <div class="input-group">
                <label for="openDate">选择打开日期:</label>
                <input type="datetime-local" id="openDate">
            </div>

            <div class="input-group">
                <label for="audioInput">录制语音(可选):</label>
                <button id="recordButton">开始录音</button>
                <audio id="audioPreview" controls style="display: none;"></audio>
            </div>

            <button id="createCapsule" class="primary-button">创建时间胶囊🥰</button>
        </div>

        <div class="capsule-list">
            <h2>我的时间胶囊⏲️</h2>
            <div id="capsules"></div>
        </div>
    </div>

    <div id="particles" class="particles"></div>
    
    <script src="script.js"></script>
</body>
</html> 

script.js(加密/录音核心逻辑)

// 时空扭曲初始化
// 加密密钥
const SECRET_KEY = 'time-capsule-secret-key';

// 录音相关变量
let mediaRecorder;
let audioChunks = [];
let isRecording = false;

// DOM 元素
const recordButton = document.getElementById('recordButton');
const audioPreview = document.getElementById('audioPreview');
const createCapsuleButton = document.getElementById('createCapsule');
const capsulesContainer = document.getElementById('capsules');

// 初始化录音功能
async function initRecording() {
    try {
        const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
        mediaRecorder = new MediaRecorder(stream);
        
        mediaRecorder.ondataavailable = (event) => {
            audioChunks.push(event.data);
        };

        mediaRecorder.onstop = () => {
            const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
            const audioUrl = URL.createObjectURL(audioBlob);
            audioPreview.src = audioUrl;
            audioPreview.style.display = 'block';
        };
    } catch (err) {
        console.error('录音初始化失败:', err);
        alert('无法访问麦克风,请确保已授予权限。');
    }
}

// 切换录音状态
function toggleRecording() {
    if (!mediaRecorder) return;

    if (!isRecording) {
        audioChunks = [];
        mediaRecorder.start();
        recordButton.textContent = '停止录音';
        isRecording = true;
    } else {
        mediaRecorder.stop();
        recordButton.textContent = '开始录音';
        isRecording = false;
    }
}

// 加密数据
function encryptData(data) {
    return CryptoJS.AES.encrypt(JSON.stringify(data), SECRET_KEY).toString();
}

// 解密数据
function decryptData(encryptedData) {
    const bytes = CryptoJS.AES.decrypt(encryptedData, SECRET_KEY);
    return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
}

// 创建时间胶囊
function createCapsule() {
    const message = document.getElementById('message').value;
    const openDate = document.getElementById('openDate').value;
    const audioUrl = audioPreview.src;

    if (!message || !openDate) {
        alert('请填写消息和打开日期!');
        return;
    }

    const capsule = {
        message,
        openDate,
        audioUrl,
        createdAt: new Date().toISOString()
    };

    const encryptedCapsule = encryptData(capsule);
    const capsules = JSON.parse(localStorage.getItem('capsules') || '[]');
    capsules.push(encryptedCapsule);
    localStorage.setItem('capsules', JSON.stringify(capsules));

    alert('时间胶囊创建成功!');
    loadCapsules();
    resetForm();
}

// 重置表单
function resetForm() {
    document.getElementById('message').value = '';
    document.getElementById('openDate').value = '';
    audioPreview.style.display = 'none';
    audioPreview.src = '';
}

// 加载时间胶囊列表
function loadCapsules() {
    const capsules = JSON.parse(localStorage.getItem('capsules') || '[]');
    capsulesContainer.innerHTML = '';

    capsules.forEach((encryptedCapsule, index) => {
        const capsule = decryptData(encryptedCapsule);
        const openDate = new Date(capsule.openDate);
        const now = new Date();

        const capsuleElement = document.createElement('div');
        capsuleElement.className = 'capsule-item';
        
        if (openDate <= now) {
            capsuleElement.innerHTML = `
                <h3>已到打开时间!</h3>
                <p>${capsule.message}</p>
                ${capsule.audioUrl ? `<audio src="${capsule.audioUrl}" controls></audio>` : ''}
                <button onclick="openCapsule(${index})">打开胶囊</button>
            `;
        } else {
            const timeLeft = openDate - now;
            const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24));
            capsuleElement.innerHTML = `
                <h3>等待打开中...</h3>
                <p>距离打开还有 ${days} 天</p>
                <p>创建时间:${new Date(capsule.createdAt).toLocaleString()}</p>
            `;
        }

        capsulesContainer.appendChild(capsuleElement);
    });
}

// 打开时间胶囊
function openCapsule(index) {
    const capsules = JSON.parse(localStorage.getItem('capsules') || '[]');
    const capsule = decryptData(capsules[index]);
    
    // 创建粒子效果
    createParticles();
    
    // 播放音效
    const audio = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-magical-coin-win-1936.mp3');
    audio.play();

    // 显示消息
    alert(`来自过去的消息:\n\n${capsule.message}`);
    
    // 从存储中移除已打开的胶囊
    capsules.splice(index, 1);
    localStorage.setItem('capsules', JSON.stringify(capsules));
    loadCapsules();
}

// 创建粒子效果
function createParticles() {
    const particlesContainer = document.getElementById('particles');
    particlesContainer.innerHTML = '';

    for (let i = 0; i < 50; i++) {
        const particle = document.createElement('div');
        particle.className = 'particle';
        particle.style.left = `${Math.random() * 100}%`;
        particle.style.top = `${Math.random() * 100}%`;
        particle.style.backgroundColor = `hsl(${Math.random() * 360}, 100%, 50%)`;
        particlesContainer.appendChild(particle);
    }

    setTimeout(() => {
        particlesContainer.innerHTML = '';
    }, 1000);
}

// 事件监听
recordButton.addEventListener('click', toggleRecording);
createCapsuleButton.addEventListener('click', createCapsule);

// 初始化
initRecording();
loadCapsules();

// 定期检查胶囊状态
setInterval(loadCapsules, 60000); // 每分钟检查一次 

styles.css(淡蓝色主题宇宙)

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

:root {
    --primary-color: #4a90e2;
    --primary-light: #e8f1fc;
    --primary-dark: #2c5282;
    --accent-color: #63b3ed;
    --text-color: #2d3748;
    --text-light: #718096;
    --background: #f7fafc;
    --white: #ffffff;
    --shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
    --transition: all 0.3s ease;
}

body {
    font-family: 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif;
    background: linear-gradient(135deg, var(--background) 0%, var(--primary-light) 100%);
    min-height: 100vh;
    padding: 20px;
    color: var(--text-color);
    line-height: 1.6;
}

.container {
    max-width: 800px;
    margin: 0 auto;
    background: var(--white);
    padding: 40px;
    border-radius: 20px;
    box-shadow: var(--shadow);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.2);
}

h1 {
    text-align: center;
    color: var(--primary-dark);
    margin-bottom: 40px;
    font-size: 2.5em;
    font-weight: 700;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}

h2 {
    color: var(--primary-dark);
    margin-bottom: 20px;
    font-size: 1.8em;
}

.input-group {
    margin-bottom: 25px;
    position: relative;
}

label {
    display: block;
    margin-bottom: 10px;
    color: var(--text-color);
    font-weight: 600;
    font-size: 1.1em;
}

textarea, input[type="datetime-local"] {
    width: 100%;
    padding: 15px;
    border: 2px solid var(--primary-light);
    border-radius: 12px;
    font-size: 16px;
    transition: var(--transition);
    background: var(--white);
    color: var(--text-color);
}

textarea:focus, input[type="datetime-local"]:focus {
    border-color: var(--primary-color);
    outline: none;
    box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);
}

textarea {
    min-height: 120px;
    resize: vertical;
}

.primary-button {
    background: var(--primary-color);
    color: var(--white);
    border: none;
    padding: 15px 30px;
    border-radius: 12px;
    cursor: pointer;
    font-size: 1.1em;
    font-weight: 600;
    width: 100%;
    transition: var(--transition);
    text-transform: uppercase;
    letter-spacing: 1px;
}

.primary-button:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
    box-shadow: 0 6px 12px rgba(74, 144, 226, 0.2);
}

#recordButton {
    background: var(--accent-color);
    color: var(--white);
    border: none;
    padding: 12px 24px;
    border-radius: 10px;
    cursor: pointer;
    margin-bottom: 15px;
    font-weight: 600;
    transition: var(--transition);
}

#recordButton:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
}

.capsule-list {
    margin-top: 50px;
}

.capsule-item {
    background: var(--primary-light);
    padding: 25px;
    border-radius: 15px;
    margin-bottom: 20px;
    border-left: 5px solid var(--primary-color);
    transition: var(--transition);
    box-shadow: var(--shadow);
}

.capsule-item:hover {
    transform: translateX(5px);
    box-shadow: 0 6px 12px rgba(74, 144, 226, 0.15);
}

.capsule-item h3 {
    color: var(--primary-dark);
    margin-bottom: 15px;
    font-size: 1.3em;
}

.capsule-item p {
    color: var(--text-light);
    margin-bottom: 10px;
}

.capsule-item button {
    background: var(--primary-color);
    color: var(--white);
    border: none;
    padding: 10px 20px;
    border-radius: 8px;
    cursor: pointer;
    font-weight: 600;
    transition: var(--transition);
    margin-top: 15px;
}

.capsule-item button:hover {
    background: var(--primary-dark);
    transform: translateY(-2px);
}

.particles {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 1000;
}

@keyframes particle {
    0% {
        transform: translateY(0) rotate(0deg) scale(1);
        opacity: 1;
    }
    100% {
        transform: translateY(-100px) rotate(360deg) scale(0);
        opacity: 0;
    }
}

.particle {
    position: absolute;
    width: 8px;
    height: 8px;
    background: var(--primary-color);
    border-radius: 50%;
    animation: particle 1.5s ease-out forwards;
    box-shadow: 0 0 10px var(--primary-color);
}

/* 响应式设计 */
@media (max-width: 768px) {
    .container {
        padding: 20px;
    }

    h1 {
        font-size: 2em;
    }

    .input-group {
        margin-bottom: 20px;
    }

    textarea, input[type="datetime-local"] {
        padding: 12px;
    }
}

/* 自定义滚动条 */
::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track {
    background: var(--primary-light);
}

::-webkit-scrollbar-thumb {
    background: var(--primary-color);
    border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--primary-dark);
} 

结语:当代码遇见诗意

image.png

现在,我的时间胶囊里躺着这样一条消息:"2025年7月1日,如果你还在纠结要不要辞职创业,记得看看这条消息——当时觉得疯狂的想法,现在可能已经是新世界的入口。"

Trae: "下次要不要试试用WebGL做3D时间轴?"
我: "先让我把这个淡蓝色宇宙修好再说吧..."

(完)

演示地址:穿越时空的按钮