๐ฉ๏ธ ๊ท์ ์ด ๋์ค๋ ๋ ์จ ์ฑ ๋ง๋ค๊ธฐ: 3D ์น ๊ฐ๋ฐ์ ์ผ์ค์คํ ์ฌ์
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 ๋ฐฐ๊ฒฝ์ผ๋ก ์คํ์ผ๋ง๋ฉ๋๋ค.
/* ์์ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง */
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";
์ฌ์ฉ์ ์ธํฐํ์ด์ค
- ๊ฒ์์ฐฝ โ ์ํ๋ ๋์๋ฅผ ์ฐพ์ ๋ ์จ๊ฐ ์ด์๋๋ ๋ชจ์ต์ ํ์ธํ์ธ์.
- ํ๋จ ๋ฐ โ ๋น ๋ฅธ ์ ๊ทผ ๋ฒํผ:
- ๐ ํ โ ์นด๋ฉ๋ผ๋ฅผ ์ด๊ธฐ ์์น๋ก ์ฌ์ค์ ํฉ๋๋ค.
- โน๏ธ ์ ๋ณด โ ์ฑ ์ ๋ณด์ ํฌ๋ ๋ง์ด ํฌํจ๋ ๋ชจ๋ฌ์ ์ฝ๋๋ค.
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 ์คํ