๐ฉ๏ธ Haunted Weather App ๋ง๋ค๊ธฐ: 3D Web Development์ ์ผ์ค์คํ ์ฌ์
Source: Dev.to
์๊ฐ
์๊ฐ๊ฐ ๋ผ๊ณ ๋ฒ๊ฐ๊ฐ ์น๋ฉด, ์ด์ ๋ ์ด์ ์บ์์ค์ ์์ง ์๋ค๋ ๊ฑธ ์ ์ ์์ต๋๋ค. ์ ๋ ๋ถ์๊ธฐ ์๋ ์๊ฐ ํจ๊ณผ์ ์ค์ฉ์ ์ธ ๊ธฐ๋ฅ์ ๊ฒฐํฉํ ๋ฌด์ธ๊ฐ๋ฅผ ๋ง๋ค๊ณ ์ถ์์ต๋๋คโ๋ฌด์ญ๊ฒ ๋๊ปด์ง๋ ๋ ์จ ์ฑ ๋ง์ด์ฃ . ๊ทธ ๊ฒฐ๊ณผ๊ฐ Eerie Weather App์ ๋๋ค. ์ ์ธ๊ณ ๋์๋ค์ ์ค์๊ฐ ๊ธฐ์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก, ์ ๋ น ๊ฐ์ ๋ ๋ค๋๋ ์ง์ด ๋ฐ์ํ๋ 3D ๋ ์จ ์๊ฐํ ์ฑ์ ๋๋ค. ๋น๊ฐ ์ค๋ฉด ํ๋์ด ์ด๋์์ง๊ณ ๋ฌผ๋ฐฉ์ธ์ด ์ถ๊ฐ๋๋ฉฐ, ์ฒ๋ฅ๋ฒ๊ฐ๊ฐ ์น๋ฉด ๋พฐ์กฑํ ๋ฒ๊ฐ๊ฐ ๋ํ๋๊ณ , ๊ฐ ๋ ์จ ์ ํ๋ง๋ค ๊ณ ์ ํ ํํฐํด ์์คํ ๊ณผ ์ฃผ๋ณ ์ฌ์ด๋๊ฐ ์์ต๋๋ค.
๋์์ธ
์์ ๊ตฌ์ฑ
- ๋ฐฐ๊ฒฝ:
#1a1a2e(๊น์ ํ๋โ๋ณด๋ผ) โ ํญํ ์ ํ๋๊ณผ ๊ฐ์ต๋๋ค. - ๊ฐ์กฐ:
#460809โ#f4320b(hover ์) โ ํผ์ฒ๋ผ ๋ถ์ ๊ฐ์กฐ์. - ํ ์คํธ ๊ธ๋ก์ฐ: ๋งฅ๋ํ๋ ํ ์คํธ ๊ทธ๋ฆผ์๋ฅผ ๊ฐ์ง ์ํ ๋ฆฌ์ผ ๋ ๋.
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); // ๋ฌ๋น ๋ฒ ์ด์ค
const directionalLight = new THREE.DirectionalLight(0xaaaadd, 0.3); // ๋ถ๋๋ฌ์ด ๋ฌ๋น
const rimLight = new THREE.DirectionalLight(0x6666aa, 0.2); // ์์ํ ์ญ๊ด
๋ฒ๊ฐ ํจ๊ณผ
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");
}
๋ฌธ์ ํด๊ฒฐ
| ๋ฌธ์ | ํด๊ฒฐ์ฑ |
|---|---|
| ๋ฒ๊ฐ๊ฐ โComputed radius is NaNโ ์ค๋ฅ๋ฅผ ์ผ์ผํด | ๊ธฐํํ์ ๋ง๋ค๊ธฐ ์ ์ ์ขํ๋ฅผ ๊ฒ์ฆ: `if (isNaN(startX) |
๊ธ๊ฒฉํ ๋ ์จ ๋ณํ๊ฐ play()/pause() ์ถฉ๋์ ์ ๋ฐ | ํ์ฌ ์ฌ์ ์ค์ธ ์ฌ์ด๋๋ฅผ ์ถ์ ํ๊ณ ๋ค๋ฅธ ์ฌ์ด๋๋ง ์ผ์์ ์ง: Object.values(weatherSounds).forEach(s => { if (s !== sound) { s.pause(); s.currentTime = 0; } }); |
| ๋์ ๊ฒ์์ด ์๋ ๋ ์จ ์ ํ์ ๋ฎ์ด์ | ์ API ๋ฐ์ดํฐ๋ฅผ ํ์ํ๊ธฐ ์ ์ manualOverride ํ๋๊ทธ๋ฅผ ์ด๊ธฐํ: manualOverride = false; displayWeatherInfo(data); |
๊ฒฐ๋ก
Eerie Weather App์ด ์ด์ ๋ผ์ด๋ธ๋ฉ๋๋ค. ์ํ๋ ๋์๋ฅผ ๊ฒ์ํ๊ณ , ์ฒ๋ฅํญํ ๊ฐ๋๋ฅผ ๋์ด๋ฉฐ, ๋ฒ๊ฐ๊ฐ ํ๋ฉด์ ๋ฐํ๋ ๋ชจ์ต์ ์ฆ๊ฒจ๋ณด์ธ์. Three.js ์๊ฐ์ ์ฐพ๋ , ๊ฐ์ฅ ๊ทน์ ์ธ ๋ฐฉ์์ผ๋ก ๋ ์จ๋ฅผ ํ์ธํ๊ณ ์ถ๋ , ์ด ์ฑ์ด ์ฌ๋ฌ๋ถ์ ๋ง์กฑ์ํฌ ๊ฒ์ ๋๋ค.
๐ฉ๏ธ Eerie Weather App ์คํํ๊ธฐ