Vibration
The Vibration component provides an interface to the Vibration API, allowing your Progressive Web App to trigger haptic feedback on devices that support it. This creates tactile sensations that enhance user experience and provide physical feedback for actions.
This component is particularly useful for:
Gaming applications requiring haptic feedback
Notification and alert systems
Form validation feedback
Interactive buttons and controls
Accessibility features for users with visual impairments
Timers and alarm applications
Browser Support
The Vibration API is supported on most Android devices in Chrome, Firefox, and Edge. iOS devices (iPhone, iPad) do not support the Vibration API due to platform restrictions.
Usage
Basic Vibration
<div {{ stimulus_controller('@pwa/vibration') }}>
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [200]}) }}>
Short Vibration (200ms)
</button>
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [500]}) }}>
Long Vibration (500ms)
</button>
</div>
<script>
document.addEventListener('pwa--vibration:triggered', () => {
console.log('Vibration started');
});
</script>Vibration Patterns
<div {{ stimulus_controller('@pwa/vibration') }}>
<h3>Vibration Patterns</h3>
<!-- Simple pulse -->
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [100, 50, 100]}) }}>
Double Tap
</button>
<!-- SOS pattern -->
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {
pattern: [100, 100, 100, 100, 100, 200, 200, 100, 200, 100, 200, 200, 100, 100, 100, 100, 100]
}) }}>
SOS
</button>
<!-- Heartbeat pattern -->
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [100, 50, 100, 500]}) }}>
Heartbeat
</button>
<!-- Notification pattern -->
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [50, 100, 50, 100, 50]}) }}>
Notification
</button>
</div>Persistent Vibration with Interval
<div {{ stimulus_controller('@pwa/vibration') }}>
<h3>Repeating Vibration</h3>
<!-- Start repeating vibration -->
<button {{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {
pattern: [200, 100, 200],
interval: 2000
}) }}>
Start Repeating (every 2s)
</button>
<!-- Stop vibration -->
<button {{ stimulus_action('@pwa/vibration', 'stop', 'click') }}>
Stop Vibration
</button>
</div>
<script>
document.addEventListener('pwa--vibration:triggered', () => {
console.log('Vibration started');
});
document.addEventListener('pwa--vibration:stopped', () => {
console.log('Vibration stopped');
});
</script>Form Validation Feedback
<div {{ stimulus_controller('@pwa/vibration') }}>
<form id="login-form">
<input type="email" id="email" placeholder="Email" required>
<input type="password" id="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
<script>
const form = document.getElementById('login-form');
const controller = document.querySelector('[data-controller="@pwa/vibration"]');
form.addEventListener('submit', (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const password = document.getElementById('password').value;
if (!email || !password) {
// Error vibration - three short pulses
controller.dispatchEvent(new CustomEvent('vibrate', {
detail: {
params: {
pattern: [50, 50, 50, 50, 50]
}
}
}));
alert('Please fill in all fields');
} else {
// Success vibration - single smooth pulse
controller.dispatchEvent(new CustomEvent('vibrate', {
detail: {
params: {
pattern: [100]
}
}
}));
// Process login...
}
});
</script>Gaming Controls
<div {{ stimulus_controller('@pwa/vibration') }}>
<div class="game-container">
<canvas id="game-canvas"></canvas>
<div class="game-controls">
<button id="shoot-btn">Shoot</button>
<button id="jump-btn">Jump</button>
</div>
</div>
</div>
<script>
const controller = document.querySelector('[data-controller="@pwa/vibration"]');
function vibratePattern(pattern) {
controller.dispatchEvent(new CustomEvent('vibrate', {
detail: { params: { pattern } }
}));
}
// Shoot action - quick sharp vibration
document.getElementById('shoot-btn').addEventListener('click', () => {
vibratePattern([30]);
// Game logic for shooting
});
// Jump action - light bounce vibration
document.getElementById('jump-btn').addEventListener('click', () => {
vibratePattern([50, 50, 30]);
// Game logic for jumping
});
// Collision event - strong impact vibration
function onCollision() {
vibratePattern([100, 50, 100]);
}
// Power-up collected - ascending vibration
function onPowerUp() {
vibratePattern([50, 30, 70, 30, 90]);
}
// Game over - descending vibration
function onGameOver() {
vibratePattern([200, 100, 150, 100, 100]);
}
</script>Timer/Alarm Application
<div {{ stimulus_controller('@pwa/vibration') }}>
<div class="timer-app">
<h2>Pomodoro Timer</h2>
<div id="timer-display">25:00</div>
<button id="start-timer">Start</button>
<button id="stop-timer">Stop</button>
</div>
</div>
<script>
const controller = document.querySelector('[data-controller="@pwa/vibration"]');
let timerInterval;
let timeLeft = 25 * 60; // 25 minutes in seconds
document.getElementById('start-timer').addEventListener('click', () => {
timerInterval = setInterval(() => {
timeLeft--;
updateDisplay();
if (timeLeft <= 0) {
clearInterval(timerInterval);
onTimerComplete();
}
}, 1000);
});
document.getElementById('stop-timer').addEventListener('click', () => {
clearInterval(timerInterval);
controller.dispatchEvent(new CustomEvent('stop'));
});
function updateDisplay() {
const mins = Math.floor(timeLeft / 60);
const secs = timeLeft % 60;
document.getElementById('timer-display').textContent =
`${mins}:${secs.toString().padStart(2, '0')}`;
}
function onTimerComplete() {
// Alarm vibration pattern - repeating pulses
controller.dispatchEvent(new CustomEvent('vibrate', {
detail: {
params: {
pattern: [300, 200, 300, 200, 300],
interval: 2000 // Repeat every 2 seconds
}
}
}));
alert('Timer complete!');
}
</script>Accessibility Features
<div {{ stimulus_controller('@pwa/vibration') }}>
<div class="accessible-navigation">
<button
class="nav-item"
{{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [20]}) }}
>
Home
</button>
<button
class="nav-item"
{{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [20]}) }}
>
Settings
</button>
<button
class="nav-item active"
{{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [40, 20, 40]}) }}
>
Current Page (double pulse)
</button>
</div>
</div>
<script>
// Provide haptic feedback for important UI elements
const importantButtons = document.querySelectorAll('.important-action');
const controller = document.querySelector('[data-controller="@pwa/vibration"]');
importantButtons.forEach(button => {
button.addEventListener('click', () => {
// Distinctive vibration for important actions
controller.dispatchEvent(new CustomEvent('vibrate', {
detail: {
params: {
pattern: [50, 30, 50, 30, 50]
}
}
}));
});
});
</script>Parameters
None
Actions
vibrate
vibrateTriggers a vibration pattern on the device.
Parameters:
pattern(Array of numbers): Vibration pattern in milliseconds. Odd indices are vibration durations, even indices are pause durations.Single value:
[200]- vibrate for 200msPattern:
[100, 50, 100]- vibrate 100ms, pause 50ms, vibrate 100ms
interval(number, optional): Interval in milliseconds to repeat the pattern. If specified, the vibration will repeat until stopped or the page is closed.
{{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [200, 100, 200]}) }}With interval for repeating vibration:
{{ stimulus_action('@pwa/vibration', 'vibrate', 'click', {pattern: [100], interval: 1000}) }}stop
stopStops any ongoing vibration, including persistent vibrations started with an interval.
{{ stimulus_action('@pwa/vibration', 'stop', 'click') }}Targets
None
Events
pwa--vibration:triggered
pwa--vibration:triggeredDispatched when a vibration pattern is triggered.
No payload
Example:
document.addEventListener('pwa--vibration:triggered', () => {
console.log('Device is vibrating');
// Show visual indicator
document.body.classList.add('vibrating');
setTimeout(() => {
document.body.classList.remove('vibrating');
}, 500);
});pwa--vibration:stopped
pwa--vibration:stoppedDispatched when vibration is explicitly stopped (not when a pattern naturally completes).
No payload
Example:
document.addEventListener('pwa--vibration:stopped', () => {
console.log('Vibration stopped');
// Clear any visual indicators
document.body.classList.remove('vibrating');
});Best Practices
Be conservative: Use vibration sparingly to avoid annoying users
Keep it short: Most vibration patterns should be under 500ms total
Provide visual feedback: Always accompany vibration with visual feedback
Check support: Detect if the API is available before relying on it
Respect battery: Excessive vibration drains battery quickly
User preferences: Consider providing an option to disable vibrations
Context-appropriate: Use different patterns for different types of feedback
Common Vibration Patterns
Here are some commonly used vibration patterns:
// Notification/Alert
const notification = [50, 50, 50, 50, 50];
// Success
const success = [100];
// Error
const error = [50, 50, 50, 50, 50, 50, 50];
// Warning
const warning = [200, 100, 200];
// Click/Tap
const tap = [20];
// Double-tap
const doubleTap = [30, 50, 30];
// Long press
const longPress = [100];
// Heartbeat
const heartbeat = [100, 50, 100, 450];
// SOS (... --- ...)
const sos = [
100, 100, 100, 100, 100, // ...
200,
200, 100, 200, 100, 200, // ---
200,
100, 100, 100, 100, 100 // ...
];Device Considerations
Android
Full support in Chrome, Firefox, and Edge
Vibration strength controlled by system settings
Users can disable vibration in system settings
iOS (iPhone/iPad)
Not supported - iOS does not expose the Vibration API to web applications
System haptics are only available to native apps
Always provide visual alternatives
Desktop
Limited support - most laptops don't have vibration motors
Some gaming devices with haptic feedback may work
Not reliable for desktop PWAs
Battery Impact
Vibration uses power from both the motor and the processor. Consider these guidelines:
Short vibrations (< 100ms): Minimal impact
Medium vibrations (100-500ms): Moderate impact
Long vibrations (> 500ms): Significant impact
Repeating vibrations: High impact, avoid if possible
Testing Vibration
// Check if Vibration API is supported
if ('vibrate' in navigator) {
console.log('Vibration API is supported');
// Test vibration
navigator.vibrate(200);
} else {
console.log('Vibration API is not supported');
// Provide visual-only feedback
}Complete Example: Interactive Notification System
<div {{ stimulus_controller('@pwa/vibration') }}>
<div class="notification-system">
<h2>Notification Center</h2>
<div class="notification-types">
<button id="info-notif">Info</button>
<button id="success-notif">Success</button>
<button id="warning-notif">Warning</button>
<button id="error-notif">Error</button>
</div>
<div id="notification-display"></div>
<label>
<input type="checkbox" id="vibration-enabled" checked>
Enable Vibration Feedback
</label>
</div>
</div>
<script>
const controller = document.querySelector('[data-controller="@pwa/vibration"]');
const display = document.getElementById('notification-display');
const vibrationEnabled = document.getElementById('vibration-enabled');
const patterns = {
info: [50],
success: [100],
warning: [100, 50, 100],
error: [50, 50, 50, 50, 50, 50, 50]
};
const colors = {
info: '#3b82f6',
success: '#10b981',
warning: '#f59e0b',
error: '#ef4444'
};
function showNotification(type, message) {
// Visual feedback
display.innerHTML = `
<div class="notification ${type}" style="background: ${colors[type]}">
<strong>${type.toUpperCase()}</strong>
<p>${message}</p>
</div>
`;
// Haptic feedback
if (vibrationEnabled.checked && 'vibrate' in navigator) {
controller.dispatchEvent(new CustomEvent('vibrate', {
detail: {
params: {
pattern: patterns[type]
}
}
}));
}
// Auto-dismiss after 3 seconds
setTimeout(() => {
display.innerHTML = '';
}, 3000);
}
document.getElementById('info-notif').addEventListener('click', () => {
showNotification('info', 'New message received');
});
document.getElementById('success-notif').addEventListener('click', () => {
showNotification('success', 'Operation completed successfully');
});
document.getElementById('warning-notif').addEventListener('click', () => {
showNotification('warning', 'Please check your settings');
});
document.getElementById('error-notif').addEventListener('click', () => {
showNotification('error', 'An error occurred');
});
document.addEventListener('pwa--vibration:triggered', () => {
console.log('Haptic feedback provided');
});
</script>
<style>
.notification {
padding: 15px;
margin: 10px 0;
border-radius: 8px;
color: white;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.notification-types {
display: flex;
gap: 10px;
margin: 20px 0;
}
.notification-types button {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-weight: bold;
}
#info-notif { background: #3b82f6; color: white; }
#success-notif { background: #10b981; color: white; }
#warning-notif { background: #f59e0b; color: white; }
#error-notif { background: #ef4444; color: white; }
</style>Last updated
Was this helpful?