App thổi nến sinh nhật —
App เป่าเทียน

App เป่าเทียนวันเกิด Blow Detection Microphone Web Audio API Animation Canvas CSS Confetti Mobile React Native Flutter Production
| Technology | Platform | Blow Detection | Animation | Difficulty |
|---|---|---|---|---|
| Web Audio API + Canvas | Web Browser | AnalyserNode | Canvas/CSS | ปานกลาง |
| React + Web Audio | Web (React) | Custom Hook | Framer Motion | ปานกลาง |
| React Native + Expo | iOS Android | expo-av | Reanimated | ปานกลาง |
| Flutter | iOS Android | audio_streamer | CustomPainter | ปานกลาง |
| Swift (iOS Native) | iOS | AVAudioEngine | SpriteKit | สูง |
| Kotlin (Android) | Android | AudioRecord | Canvas | สูง |
Blow Detection
=== Web Audio Blow Detection ===
JavaScript Implementation
async function initBlowDetection(onBlow) {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const audioContext = new AudioContext();
const source = audioContext.createMediaStreamSource(stream);
const analyser = audioContext.createAnalyser();
analyser.fftSize = 2048;
analyser.smoothingTimeConstant = 0.8;
source.connect(analyser);
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const BLOW_THRESHOLD = 150; // 0-255
const LOW_FREQ_START = 5; // ~200Hz
const LOW_FREQ_END = 15; // ~500Hz
let isBlowing = false;
let blowTimeout = null;
function detectBlow() {
analyser.getByteFrequencyData(dataArray);
Focus on low frequencies (blow sound range)
let sum = 0;
for (let i = LOW_FREQ_START; i < LOW_FREQ_END; i++) {
sum += dataArray[i];
}
const average = sum / (LOW_FREQ_END - LOW_FREQ_START);
if (average > BLOW_THRESHOLD && !isBlowing) {
isBlowing = true;
onBlow();
blowTimeout = setTimeout(() => { isBlowing = false; }, 1000);
}
requestAnimationFrame(detectBlow);
}
detectBlow();
}
Usage
initBlowDetection(() => {
console.log('Blow detected! Extinguish candles!');
extinguishCandles();
เนื้อหาเกี่ยวข้อง — Overclock RAM — คู่มือฉบับสมบูรณ์ 2026
});
from dataclasses import dataclass
@dataclass
class AudioConfig:
param: str
value: str
purpose: str
configs = [
AudioConfig("fftSize", "2048", "Frequency resolution — higher = more precise"),
AudioConfig("smoothingTimeConstant", "0.8", "Smooth frequency data — reduce noise"),
AudioConfig("BLOW_THRESHOLD", "150 (0-255)", "Volume level to trigger blow detection"),
แนะนำเพิ่มเติม — หนังสือเทรดที่ SiamCafeBook
AudioConfig("LOW_FREQ_START", "5 (~200Hz)", "Start of blow frequency range"),
AudioConfig("LOW_FREQ_END", "15 (~500Hz)", "End of blow frequency range"),
AudioConfig("Debounce", "1000ms", "Prevent multiple triggers from single blow"),
]
print("=== Audio Configuration ===")
for c in configs:
print(f" [{c.param}] Value: {c.value}")
print(f" Purpose: {c.purpose}")
Candle Animation
=== CSS Candle Animation ===
/* Candle Base */
.candle {
width: 30px;
height: 120px;
background: linear-gradient(to bottom, #fff3b0, #ffd700);
border-radius: 3px 3px 0 0;
position: relative;
margin: 0 15px;
}
/* Flame */
.flame {
width: 20px;
height: 40px;
background: radial-gradient(ellipse at bottom,
#ff6600 0%, #ffcc00 40%, transparent 70%);
border-radius: 50% 50% 30% 30%;
position: absolute;
top: -45px;
left: 50%;
transform: translateX(-50%);
เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ Go Cobra CLI Machine Learning Pipeline
animation: flicker 0.3s infinite alternate;
transition: opacity 0.5s, transform 0.5s;
}
@keyframes flicker {
0% { transform: translateX(-50%) scale(1) rotate(-2deg); }
50% { transform: translateX(-50%) scale(1.05) rotate(1deg); }
100% { transform: translateX(-50%) scale(0.95) rotate(-1deg); }
}
/* Extinguished */
.flame.out {
opacity: 0;
transform: translateX(-50%) scale(0);
}
/* Smoke */
.smoke {
width: 4px;
height: 30px;
background: rgba(150, 150, 150, 0.3);
position: absolute;
top: -50px;
แนะนำเพิ่มเติม — สัญญาณเทรดรายวัน XM Signal
left: 50%;
transform: translateX(-50%);
opacity: 0;
animation: smoke-rise 2s ease-out forwards;
}
@keyframes smoke-rise {
0% { opacity: 0.8; transform: translateX(-50%) translateY(0); }
100% { opacity: 0; transform: translateX(-50%) translateY(-40px); }
}
Confetti Animation (JavaScript)
function createConfetti(count = 100) {
for (let i = 0; i < count; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + 'vw';
confetti.style.background = `hsl(, 80%, 60%)`;
confetti.style.animationDuration = Math.random() * 2 + 1 + 's';
confetti.style.animationDelay = Math.random() * 0.5 + 's';
เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ หุ้น บัวหลวง กับเทรนด์เทคโนโลยีใหม่: โอกาสการลงทุนในยุคดิจิทัล
document.body.appendChild(confetti);
setTimeout(() => confetti.remove(), 3000);
}
}
@dataclass
class AnimationLayer:
element: str
technique: str
trigger: str
duration: str
layers = [
AnimationLayer("Flame flicker", "CSS @keyframes + transform", "Always (while lit)", "0.3s infinite"),
AnimationLayer("Flame glow", "CSS radial-gradient + box-shadow", "Always (while lit)", "0.5s infinite"),
AnimationLayer("Extinguish", "CSS transition opacity + scale", "On blow detect", "0.5s ease-out"),
AnimationLayer("Smoke trail", "CSS @keyframes translateY", "After extinguish", "2s ease-out"),
AnimationLayer("Confetti", "JS DOM + CSS @keyframes", "All candles out", "1-3s random"),
AnimationLayer("Happy Birthday", "Audio play()", "All candles out", "30s"),
]
print("\n=== Animation Layers ===")
for a in layers:
print(f" [{a.element}] Tech: {a.technique}")
print(f" Trigger: {a.trigger} | Duration: {a.duration}")
Mobile Development

=== React Native / Flutter Implementation ===
React Native (Expo) — Blow Detection
import { Audio } from 'expo-av';
const startBlowDetection = async () => {
const { status } = await Audio.requestPermissionsAsync();
if (status !== 'granted') return;
const recording = new Audio.Recording();
await recording.prepareToRecordAsync({
android: { ...Audio.RecordingOptionsPresets.HIGH_QUALITY.android },
ios: { ...Audio.RecordingOptionsPresets.HIGH_QUALITY.ios },
});
recording.setOnRecordingStatusUpdate((status) => {
if (status.metering && status.metering > -20) {
Blow detected (metering is in dB, -160 to 0)
handleBlow();
}
});
await recording.startAsync();
เนื้อหาเกี่ยวข้อง — แนะนำให้อ่าน เหรียญ ADA — คู่มือฉบับสมบูรณ์ 2026
};
Flutter — Blow Detection
import 'package:audio_streamer/audio_streamer.dart';
class BlowDetector {
final AudioStreamer _streamer = AudioStreamer();
final double _threshold = 0.5;
void start(VoidCallback onBlow) {
_streamer.start((List<double> samples) {
double rms = _calculateRMS(samples);
if (rms > _threshold) {
onBlow();
}
});
}
double _calculateRMS(List<double> samples) {
double sum = 0;
for (var s in samples) { sum += s * s; }
return sqrt(sum / samples.length);
}
}
@dataclass
class AppFeature:
feature: str
web: str
react_native: str
flutter: str
features = [
AppFeature("Mic Access", "getUserMedia()", "expo-av Audio", "audio_streamer"),
AppFeature("Blow Detect", "AnalyserNode FFT", "metering dB", "RMS calculation"),
AppFeature("Animation", "CSS + Canvas", "Reanimated + Lottie", "CustomPainter"),
AppFeature("Haptic", "navigator.vibrate()", "expo-haptics", "HapticFeedback"),
AppFeature("Sound", "Audio() play()", "expo-av Sound", "audioplayers"),
AppFeature("Share", "Web Share API", "expo-sharing", "share_plus"),
]
print("Cross-platform Comparison:")
for f in features:
print(f" [{f.feature}]")
print(f" Web: {f.web} | RN: {f.react_native} | Flutter: {f.flutter}")
เคล็ดลับ
- Frequency: กรอง Low Frequency 200-500Hz สำหรับลมเป่า
- Debounce: ตั้ง Debounce 1 วินาทีป้องกัน Trigger ซ้ำ
- Permission: ขอสิทธิ์ Microphone ก่อนเริ่มเสมอ
- Animation: ใช้ CSS Animation สำหรับ Performance ที่ดี
- UX: เพิ่ม Fallback ปุ่มกดสำหรับกรณี Mic ไม่ทำงาน
App เป่าเทียนวันเกิดคืออะไร
แอป Microphone ตรวจลมเป่า Blow Detection Web Audio API เทียนดับ Animation เปลวไฟ ควัน Confetti Canvas CSS วันเกิด ปาร์ตี้ อวยพร





