Technology

app thổi nến sinh nhật

app thổi nến sinh nhật
app thổi nến sinh nhật | SiamCafe Blog
2026-01-13· อ. บอม — SiamCafe.net· 11,402 คำ

App เป่าเทียน

App เป่าเทียนวันเกิด Blow Detection Microphone Web Audio API Animation Canvas CSS Confetti Mobile React Native Flutter Production

TechnologyPlatformBlow DetectionAnimationDifficulty
Web Audio API + CanvasWeb BrowserAnalyserNodeCanvas/CSSปานกลาง
React + Web AudioWeb (React)Custom HookFramer Motionปานกลาง
React Native + ExpoiOS Androidexpo-avReanimatedปานกลาง
FlutteriOS Androidaudio_streamerCustomPainterปานกลาง
Swift (iOS Native)iOSAVAudioEngineSpriteKitสูง
Kotlin (Android)AndroidAudioRecordCanvasสูง

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();
# });

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"),
    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%);
#   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;
#   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();
# };

# 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 samples) {
#       double rms = _calculateRMS(samples);
#       if (rms > _threshold) {
#         onBlow();
#       }
#     });
#   }
#
#   double _calculateRMS(List 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}")

เคล็ดลับ

App เป่าเทียนวันเกิดคืออะไร

แอป Microphone ตรวจลมเป่า Blow Detection Web Audio API เทียนดับ Animation เปลวไฟ ควัน Confetti Canvas CSS วันเกิด ปาร์ตี้ อวยพร

ใช้เทคโนโลยีอะไร

Web Audio API getUserMedia AnalyserNode Canvas CSS Animation React Native expo-av Flutter audio_streamer Haptic Feedback requestAnimationFrame

ทำ Blow Detection อย่างไร

getUserMedia audio AudioContext AnalyserNode getByteFrequencyData Volume Threshold 150 Low Frequency 200-500Hz Debounce requestAnimationFrame Loop

สร้าง Animation เทียนอย่างไร

CSS @keyframes flicker Gradient ส้มเหลืองแดง Transition opacity scale Smoke rise Confetti DOM Canvas Particle Happy Birthday Audio

สรุป

App เป่าเทียนวันเกิด Blow Detection Microphone Web Audio API AnalyserNode Animation CSS Canvas Confetti React Native Flutter Mobile Production

📖 บทความที่เกี่ยวข้อง

nến cây sinh nhậtอ่านบทความ → hình ảnh thổi nến sinh nhậtอ่านบทความ → thổi nến sinh nhật onlineอ่านบทความ → hình ảnh nến sinh nhậtอ่านบทความ → thổi nến bánh sinh nhật onlineอ่านบทความ →

📚 ดูบทความทั้งหมด →