The Vibration API allows web pages to control the device’s vibration feature. This article covers its usage and practical applications.

Basic Concepts

Detecting Support

// Check API support
if ('vibrate' in navigator) {
  console.log('Vibration API is supported');
} else {
  console.log('Vibration API is not supported');
}

// Safe vibration function
function vibrate(pattern) {
  if ('vibrate' in navigator) {
    return navigator.vibrate(pattern);
  }
  return false;
}

Simple Vibration

// Vibrate for 200 milliseconds
navigator.vibrate(200);

// Vibrate for 1 second
navigator.vibrate(1000);

// Stop vibration
navigator.vibrate(0);
// or
navigator.vibrate([]);

Vibration Patterns

// Pattern: [vibrate, pause, vibrate, pause, ...]
// Vibrate 100ms, pause 50ms, vibrate 100ms
navigator.vibrate([100, 50, 100]);

// SOS Morse code
// ... --- ... (3 short, 3 long, 3 short)
const sos = [
  100, 50, 100, 50, 100, // S: ...
  200,                    // gap
  300, 50, 300, 50, 300, // O: ---
  200,                    // gap
  100, 50, 100, 50, 100  // S: ...
];
navigator.vibrate(sos);

// Heartbeat pattern
const heartbeat = [100, 100, 100, 400];
navigator.vibrate(heartbeat);

Vibration Manager

Wrapper Class

class VibrationManager {
  constructor() {
    this.isSupported = 'vibrate' in navigator;
    this.isEnabled = true;
    this.currentPattern = null;
  }
  
  // Check if vibration is possible
  canVibrate() {
    return this.isSupported && this.isEnabled;
  }
  
  // Enable/disable vibration
  setEnabled(enabled) {
    this.isEnabled = enabled;
    if (!enabled) {
      this.stop();
    }
  }
  
  // Simple vibration
  vibrate(duration = 100) {
    if (!this.canVibrate()) return false;
    return navigator.vibrate(duration);
  }
  
  // Pattern vibration
  pattern(pattern) {
    if (!this.canVibrate()) return false;
    this.currentPattern = pattern;
    return navigator.vibrate(pattern);
  }
  
  // Repeat pattern
  repeat(pattern, times) {
    if (!this.canVibrate()) return false;
    
    const repeatedPattern = [];
    for (let i = 0; i < times; i++) {
      repeatedPattern.push(...pattern);
      if (i < times - 1) {
        // Add gap
        repeatedPattern.push(pattern[pattern.length - 1] || 100);
      }
    }
    
    return navigator.vibrate(repeatedPattern);
  }
  
  // Stop vibration
  stop() {
    if (this.isSupported) {
      navigator.vibrate(0);
      this.currentPattern = null;
    }
  }
}

// Usage
const vibration = new VibrationManager();

// Simple vibration
vibration.vibrate(200);

// Pattern vibration
vibration.pattern([100, 50, 100]);

// Repeat 3 times
vibration.repeat([100, 50, 100], 3);

// Disable vibration
vibration.setEnabled(false);

Preset Pattern Library

class VibrationPatterns {
  static patterns = {
    // Notification types
    notification: [100, 50, 100],
    success: [50, 50, 100],
    error: [100, 50, 100, 50, 100],
    warning: [200, 100, 200],
    
    // Interaction feedback
    tap: [10],
    doubleTap: [10, 50, 10],
    longPress: [50],
    
    // Game effects
    explosion: [100, 30, 50, 30, 200],
    hit: [30],
    powerUp: [50, 30, 50, 30, 100],
    gameOver: [200, 100, 200, 100, 500],
    
    // Special patterns
    heartbeat: [100, 100, 100, 400],
    alarm: [500, 200, 500, 200, 500],
    sos: [100, 50, 100, 50, 100, 200, 300, 50, 300, 50, 300, 200, 100, 50, 100, 50, 100],
    
    // Musical rhythms
    rhythm1: [100, 100, 100, 100, 200, 200],
    rhythm2: [50, 50, 50, 50, 100, 100, 200]
  };
  
  static get(name) {
    return this.patterns[name] || [100];
  }
  
  static play(name) {
    const pattern = this.get(name);
    if ('vibrate' in navigator) {
      navigator.vibrate(pattern);
    }
  }
  
  static register(name, pattern) {
    this.patterns[name] = pattern;
  }
  
  static list() {
    return Object.keys(this.patterns);
  }
}

// Usage
VibrationPatterns.play('success');
VibrationPatterns.play('heartbeat');

// Register custom pattern
VibrationPatterns.register('custom', [75, 25, 75, 25, 150]);
VibrationPatterns.play('custom');

Practical Applications

Haptic Feedback Button

class HapticButton {
  constructor(element, options = {}) {
    this.element = element;
    this.vibration = new VibrationManager();
    this.pattern = options.pattern || [10];
    this.enabled = options.enabled !== false;
    
    this.bindEvents();
  }
  
  bindEvents() {
    this.element.addEventListener('click', () => {
      this.onClick();
    });
    
    this.element.addEventListener('touchstart', () => {
      this.onTouchStart();
    });
  }
  
  onClick() {
    if (this.enabled) {
      this.vibration.pattern(this.pattern);
    }
  }
  
  onTouchStart() {
    if (this.enabled) {
      this.vibration.vibrate(5);
    }
  }
  
  setEnabled(enabled) {
    this.enabled = enabled;
  }
  
  setPattern(pattern) {
    this.pattern = pattern;
  }
}

// Usage
document.querySelectorAll('.haptic-btn').forEach(btn => {
  new HapticButton(btn, {
    pattern: [15],
    enabled: true
  });
});

// Different buttons with different feedback
new HapticButton(document.getElementById('submit-btn'), {
  pattern: VibrationPatterns.get('success')
});

new HapticButton(document.getElementById('delete-btn'), {
  pattern: VibrationPatterns.get('warning')
});

Game Vibration Feedback

class GameHaptics {
  constructor() {
    this.vibration = new VibrationManager();
    this.intensityMultiplier = 1;
  }
  
  // Set intensity multiplier
  setIntensity(multiplier) {
    this.intensityMultiplier = Math.max(0, Math.min(2, multiplier));
  }
  
  // Apply intensity
  applyIntensity(pattern) {
    return pattern.map(duration => 
      Math.round(duration * this.intensityMultiplier)
    );
  }
  
  // Collision feedback
  collision(force = 1) {
    const baseDuration = 30;
    const duration = Math.round(baseDuration * force * this.intensityMultiplier);
    this.vibration.vibrate(Math.min(duration, 200));
  }
  
  // Explosion effect
  explosion(intensity = 1) {
    const pattern = this.applyIntensity([
      100 * intensity,
      30,
      50 * intensity,
      30,
      200 * intensity
    ]);
    this.vibration.pattern(pattern);
  }
  
  // Hit effect
  hit(damage = 1) {
    const duration = Math.round(20 + damage * 30);
    this.vibration.vibrate(Math.min(duration, 150));
  }
  
  // Item pickup
  pickup() {
    this.vibration.pattern(this.applyIntensity([30, 30, 50]));
  }
  
  // Level up effect
  levelUp() {
    this.vibration.pattern(this.applyIntensity([50, 50, 50, 50, 100, 100, 200]));
  }
  
  // Game over
  gameOver() {
    this.vibration.pattern(this.applyIntensity([200, 100, 200, 100, 500]));
  }
  
  // Victory effect
  victory() {
    this.vibration.pattern(this.applyIntensity([
      100, 50, 100, 50, 100, 100,
      200, 100, 200, 100,
      300
    ]));
  }
  
  // Countdown
  countdown(remaining) {
    if (remaining <= 3) {
      this.vibration.vibrate(100 + (4 - remaining) * 50);
    }
  }
}

// Usage
const haptics = new GameHaptics();
haptics.setIntensity(1.2);

// Game events
game.on('collision', (force) => haptics.collision(force));
game.on('explosion', () => haptics.explosion());
game.on('hit', (damage) => haptics.hit(damage));
game.on('pickup', () => haptics.pickup());
game.on('levelUp', () => haptics.levelUp());
game.on('gameOver', () => haptics.gameOver());

Form Validation Feedback

class FormHapticFeedback {
  constructor(form) {
    this.form = form;
    this.vibration = new VibrationManager();
    
    this.bindEvents();
  }
  
  bindEvents() {
    // Form submission
    this.form.addEventListener('submit', (e) => {
      if (!this.form.checkValidity()) {
        e.preventDefault();
        this.onValidationError();
      } else {
        this.onSubmitSuccess();
      }
    });
    
    // Field validation
    this.form.querySelectorAll('input, select, textarea').forEach(field => {
      field.addEventListener('blur', () => {
        if (!field.checkValidity()) {
          this.onFieldError(field);
        }
      });
      
      field.addEventListener('input', () => {
        if (field.checkValidity() && field.value) {
          this.onFieldValid(field);
        }
      });
    });
  }
  
  onValidationError() {
    this.vibration.pattern([50, 30, 50, 30, 100]);
  }
  
  onSubmitSuccess() {
    this.vibration.pattern([50, 50, 100]);
  }
  
  onFieldError(field) {
    this.vibration.vibrate(30);
  }
  
  onFieldValid(field) {
    this.vibration.vibrate(10);
  }
}

// Usage
const form = document.getElementById('signup-form');
new FormHapticFeedback(form);

Notification Vibration

class NotificationVibration {
  constructor() {
    this.vibration = new VibrationManager();
    this.priorities = {
      low: [50],
      normal: [100, 50, 100],
      high: [200, 100, 200],
      urgent: [100, 50, 100, 50, 100, 100, 200, 100, 200]
    };
  }
  
  notify(priority = 'normal') {
    const pattern = this.priorities[priority] || this.priorities.normal;
    this.vibration.pattern(pattern);
  }
  
  message() {
    this.notify('normal');
  }
  
  call() {
    // Incoming call vibration (continuous)
    this.startRinging();
  }
  
  startRinging() {
    this.ringInterval = setInterval(() => {
      this.vibration.pattern([400, 200, 400, 1000]);
    }, 2000);
  }
  
  stopRinging() {
    if (this.ringInterval) {
      clearInterval(this.ringInterval);
      this.ringInterval = null;
      this.vibration.stop();
    }
  }
  
  alarm() {
    this.notify('urgent');
  }
  
  reminder() {
    this.notify('low');
  }
}

// Usage
const notifVibration = new NotificationVibration();

// Message received
notifVibration.message();

// Incoming call
notifVibration.call();

// Stop when answered or rejected
document.getElementById('answer-btn').addEventListener('click', () => {
  notifVibration.stopRinging();
});

Morse Code Generator

class MorseVibrator {
  constructor() {
    this.vibration = new VibrationManager();
    
    // Time unit (milliseconds)
    this.unit = 100;
    
    // Morse code table
    this.morseCode = {
      'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 
      'F': '..-.', 'G': '--.', 'H': '....', 'I': '..', 'J': '.---',
      'K': '-.-', 'L': '.-..', 'M': '--', 'N': '-.', 'O': '---',
      'P': '.--.', 'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-',
      'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', 'Y': '-.--',
      'Z': '--..', '0': '-----', '1': '.----', '2': '..---',
      '3': '...--', '4': '....-', '5': '.....', '6': '-....',
      '7': '--...', '8': '---..', '9': '----.'
    };
  }
  
  // Convert text to vibration pattern
  textToPattern(text) {
    const pattern = [];
    const upperText = text.toUpperCase();
    
    for (let i = 0; i < upperText.length; i++) {
      const char = upperText[i];
      
      if (char === ' ') {
        // Word gap (7 units)
        pattern.push(0, this.unit * 7);
      } else if (this.morseCode[char]) {
        const morse = this.morseCode[char];
        
        for (let j = 0; j < morse.length; j++) {
          if (morse[j] === '.') {
            pattern.push(this.unit); // Dot: 1 unit
          } else {
            pattern.push(this.unit * 3); // Dash: 3 units
          }
          
          // Symbol gap (1 unit)
          if (j < morse.length - 1) {
            pattern.push(this.unit);
          }
        }
        
        // Character gap (3 units)
        if (i < upperText.length - 1 && upperText[i + 1] !== ' ') {
          pattern.push(this.unit * 3);
        }
      }
    }
    
    return pattern.filter(d => d > 0);
  }
  
  // Vibrate text
  send(text) {
    const pattern = this.textToPattern(text);
    this.vibration.pattern(pattern);
    return pattern;
  }
  
  // Send SOS
  sendSOS() {
    return this.send('SOS');
  }
  
  // Set speed
  setSpeed(wpm) {
    // Standard: PARIS = 50 units, wpm PARIS per minute
    this.unit = Math.round(1200 / wpm);
  }
}

// Usage
const morse = new MorseVibrator();
morse.setSpeed(15); // 15 WPM

// Send message
morse.send('HELLO');

// Send SOS
document.getElementById('sos-btn').addEventListener('click', () => {
  morse.sendSOS();
});

Musical Rhythm Vibrator

class RhythmVibrator {
  constructor() {
    this.vibration = new VibrationManager();
    this.bpm = 120;
    this.isPlaying = false;
    this.beatInterval = null;
  }
  
  // Set BPM
  setBPM(bpm) {
    this.bpm = Math.max(30, Math.min(300, bpm));
    
    // Restart if playing
    if (this.isPlaying) {
      this.stop();
      this.start();
    }
  }
  
  // Calculate beat interval (milliseconds)
  getBeatInterval() {
    return 60000 / this.bpm;
  }
  
  // Start beat
  start() {
    if (this.isPlaying) return;
    
    this.isPlaying = true;
    const interval = this.getBeatInterval();
    
    // Vibrate immediately
    this.beat();
    
    this.beatInterval = setInterval(() => {
      this.beat();
    }, interval);
  }
  
  // Single beat
  beat() {
    this.vibration.vibrate(30);
  }
  
  // Stop
  stop() {
    this.isPlaying = false;
    if (this.beatInterval) {
      clearInterval(this.beatInterval);
      this.beatInterval = null;
    }
    this.vibration.stop();
  }
  
  // Play rhythm pattern
  playPattern(pattern, repeat = 1) {
    // pattern example: [1, 0, 1, 0, 1, 1, 0, 1]
    // 1 = vibrate, 0 = silent
    const interval = this.getBeatInterval();
    let index = 0;
    let repeatCount = 0;
    
    this.stop();
    this.isPlaying = true;
    
    const playBeat = () => {
      if (!this.isPlaying) return;
      
      if (pattern[index]) {
        this.beat();
      }
      
      index++;
      
      if (index >= pattern.length) {
        index = 0;
        repeatCount++;
        
        if (repeat > 0 && repeatCount >= repeat) {
          this.stop();
          return;
        }
      }
      
      this.beatInterval = setTimeout(playBeat, interval);
    };
    
    playBeat();
  }
}

// Usage
const rhythm = new RhythmVibrator();
rhythm.setBPM(100);

// Start beat
document.getElementById('start-rhythm').addEventListener('click', () => {
  rhythm.start();
});

// Stop
document.getElementById('stop-rhythm').addEventListener('click', () => {
  rhythm.stop();
});

// Play 4/4 rhythm
document.getElementById('pattern-44').addEventListener('click', () => {
  rhythm.playPattern([1, 0, 1, 0, 1, 0, 1, 0], 4);
});

Best Practices Summary

Vibration API Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   User Experience                                   │
│   ├── Provide option to disable vibration          │
│   ├── Keep vibration duration short                │
│   ├── Avoid overusing vibration                    │
│   └── Consider user context (meetings, etc.)       │
│                                                     │
│   Performance Considerations                        │
│   ├── Avoid continuous high-frequency vibration    │
│   ├── Long patterns may be interrupted by system   │
│   ├── Background page vibration may not work       │
│   └── Consider battery consumption                 │
│                                                     │
│   Compatibility Handling                            │
│   ├── Always check API support                     │
│   ├── Desktop browsers usually don't support       │
│   ├── Provide non-vibration alternatives           │
│   └── iOS Safari is not supported                  │
│                                                     │
└─────────────────────────────────────────────────────┘
ScenarioRecommended DurationPattern Example
Light touch5-15ms[10]
Click confirm15-30ms[20]
Success hint50-100ms[50, 30, 80]
Error warning100-200ms[100, 50, 100]
Notification200-500ms[200, 100, 200]

Master the Vibration API to enhance mobile interaction experience.