🌩️ 构建闹鬼的天气应用:一次进入 3D Web 开发的惊悚之旅
Source: Dev.to
介绍
当雾气滚滚、闪电划破天际时,你就知道已经不在堪萨斯了。我想打造一个把氛围视觉与实用功能结合在一起的作品——一个带点恐怖感的天气应用。于是诞生了 Eerie Weather App,一个 3D 天气可视化项目,漂浮的闹鬼小屋会根据全球各城市的实时天气做出反应。下雨时天空变暗并出现雨滴,雷暴时会释放锯齿状闪电,每种天气都有专属的粒子系统和环境音效。
设计
配色方案
- 背景:
#1a1a2e(深蓝紫)——像暴风雨前的天空。 - 强调色:
#460809→#f4320b(悬停时)——血红的高光。 - 文字发光: 以脉动的红色文字阴影呈现。
h3 {
color: #ff6b6b;
text-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
}
排版
“October Crow” 字体带有锯齿、手绘的感觉。
@font-face {
font-family: "October Crow";
src: url("/fonts/October Crow.ttf") format("truetype");
}
body {
font-family: "October Crow", Arial, sans-serif;
}
自定义按钮
按钮使用内联 SVG 背景,看起来像风化的石板。
/* Example background image */
background-image: url('data:image/svg+xml,');
Three.js 初始化
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
scene.fog = new THREE.Fog(0x1a1a2e, 10, 50);
雾效增加深度,营造“有什么潜伏”的氛围。
闹鬼小屋模型
loader.load("models/forest_house.glb", (gltf) => {
house = gltf.scene;
house.userData.centerY = -center.y - 2.5;
scene.add(house);
});
// Animation loop
house.position.y = house.userData.centerY + Math.sin(time * 0.5) * 0.2;
小屋会轻微上下晃动,随天气变化而响应。
灯光
const ambientLight = new THREE.AmbientLight(0x9999cc, 0.4); // Moonlit base
const directionalLight = new THREE.DirectionalLight(0xaaaadd, 0.3); // Soft moonlight
const rimLight = new THREE.DirectionalLight(0x6666aa, 0.2); // Subtle backlight
闪电效果
function createLightningBolt(startX, startY, startZ, endX, endY, endZ) {
const points = [];
const segments = 8 + Math.floor(Math.random() * 6);
for (let i = 0; i < segments; i++) {
// generate jittered points between start and end
const t = i / (segments - 1);
const x = THREE.MathUtils.lerp(startX, endX, t) + (Math.random() - 0.5) * 0.5;
const y = THREE.MathUtils.lerp(startY, endY, t) + (Math.random() - 0.5) * 0.5;
const z = THREE.MathUtils.lerp(startZ, endZ, t) + (Math.random() - 0.5) * 0.5;
points.push(new THREE.Vector3(x, y, z));
}
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 });
const line = new THREE.Line(geometry, material);
scene.add(line);
// Fade out after a short duration
setTimeout(() => scene.remove(line), 200);
}
天气数据集成
const url = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=temperature_2m,weather_code...`;
const data = await fetch(url).then(r => r.json());
将 Open‑Meteo 天气代码映射到恐怖效果:
let condition;
if ([95, 96, 99].includes(weatherCode)) condition = "Thunderstorm";
else if ([61, 63, 65].includes(weatherCode)) condition = "Rain";
else if ([71, 73, 75].includes(weatherCode)) condition = "Snow";
用户界面
- 搜索栏 – 输入任意城市,观看天气“活”起来。
- 底部栏 – 快捷按钮:
- 🏠 Home – 重置相机。
- ℹ️ About – 打开包含应用信息和致谢的模态框。
document.getElementById("home-btn").addEventListener("click", () => {
camera.position.copy(initialCameraPosition);
controls.target.copy(initialCameraTarget);
controls.update();
});
面板可以最小化:
function togglePanel(panelId) {
const panel = document.getElementById(panelId);
panel.classList.toggle("minimized");
}
故障排查
| 问题 | 解决方案 |
|---|---|
| Lightning bolts cause “Computed radius is NaN” errors | 在创建几何体前验证坐标:`if (isNaN(startX) |
Rapid weather changes trigger play()/pause() conflicts | 跟踪当前播放的音效,只暂停其他音效:Object.values(weatherSounds).forEach(s => { if (s !== sound) { s.pause(); s.currentTime = 0; } }); |
| City search overrides manual weather selection | 使用 manualOverride 标记,在显示新 API 数据前重置:manualOverride = false; displayWeatherInfo(data); |
结论
Eerie Weather App 已上线。搜索你的城市,调高雷暴强度,让闪电照亮屏幕。无论你是想找 Three.js 灵感,还是想用最戏剧化的方式查看天气,这款应用都能满足你的需求。