Skip to main content

Haptic Feedback (iOS WebView)

#Pinpoll Tools #Integration #UX

Tobias Oberascher avatar
Written by Tobias Oberascher
Updated over a week ago

Overview

The Pinpoll Widget triggers haptic feedback is using the standard navigator.vibrate method in browsers. However, to support native haptics in mobile apps (iOS/Android), the widget emits structured postMessage events that must be handled on the native side (e.g., React Native WebView).

Message Event Structure

The widget sends messages using window.postMessage with the following format:

{  type: 'pinpoll-haptic',  vibrate: number | number[]}

Parameters:

  • type (string): Always 'pinpoll-haptic' to identify the haptic events from the Pinpoll widget.

  • vibrate (number or array): Defines the vibration pattern, following the same structure as the browser Vibration API:

    • A single number (e.g., 50) β†’ vibrate for 50ms.

    • An array (e.g., [30, 20, 30]) β†’ vibrate for 30ms, pause 20ms, then vibrate 30ms.

WebView Listener Implementation

In a native app (e.g., React Native), this message must be received and handled manually:

window.addEventListener("message", function (event) {
try {
const data =
typeof event.data === "string" ? JSON.parse(event.data) : event.data
// Check for haptic feedback event
if (data.type === "pinpoll-haptic") {
// Forward message to native context if using ReactNativeWebView
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify(data))
}
}
} catch (e) {
console.warn("Message forwarding error:", e)
}
})

Attach this handler to your <WebView> component:

<WebView
source={{ uri: 'https://your-widget-url.com' }}
onMessage={handleMessage}
javaScriptEnabled
/>

Event Handling (React Native)

import * as Haptics from 'expo-haptics';
import * as Device from 'expo-device';
import { Vibration } from 'react-native';

export default function App() {
...
/**
* Handles messages from the WebView on the App level.
* @param {Object} event - The event object containing the message data.
* @returns {void}
*/
const handleMessage = (event) => {
try {
const data = JSON.parse(event.nativeEvent.data);
if (data.type === 'pinpoll-haptic') {
triggerHaptic(data.vibrate);
}
} catch (e) {
console.warn('Invalid message:', e);
}
};

/**
* Triggers haptic feedback or vibration based on the provided parameter.
* If the device is old (iPhone 6 or earlier, or iOS version < 13), it uses Vibration API.
* Otherwise, it uses Haptics API for better feedback.
* @param {number | number[]} param - A number representing the strength of the vibration or an array of patterns for sequential vibrations.
*/
const triggerHaptic = async (param) => {
try {
const isOld = Device.modelName?.includes('iPhone 6') || parseFloat(Device.osVersion) < 13;
console.log('Device:', Device.modelName, 'OS Version:', Device.osVersion, 'Using'+ (isOld ? ' Vibration' : ' Haptics'));

if (Array.isArray(param)) {
let i = 0;
const pattern = param;
const loop = () => {
if (i >= pattern.length) return;
const isVibrate = i % 2 === 0;
if (isVibrate) {
console.log('Vibrating MEDIUM with pattern:', pattern[i]);
if (isOld) {
Vibration.vibrate(pattern[i]);
}
else { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); }
}
setTimeout(loop, pattern[i]);
i++;
};
loop();
} else {
const strength = +param || 50; // Default to 50 if not a number
if (isOld) {
if (strength > 20) {
console.log('Vibrating with strength:', strength);
Vibration.vibrate(strength);
} else {
console.log('Too subtle vibration, cant vibrate with strength:', strength);
}

} else {
if (strength < 20) {
console.log('Vibrating LIGHT with strength:', strength);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
} else if (strength < 50) {
console.log('Vibrating MEDIUM with strength:', strength);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
} else {
console.log('Vibrating HEAVY with strength:', strength);
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy);
}
}
}
} catch (e) {
console.warn('triggerHaptic failed', e);
Vibration.vibrate(50);
}
};
}
Did this answer your question?