Quick answer: When a Construct 3 game runs as an installed PWA on iOS, WKWebView applies stricter gesture defaults than Safari. The system captures taps for double-tap zoom and edge swipes, firing pointercancel before your Touch event handlers see the input. Add touch-action: manipulation to the canvas wrapper, attach a non-passive touchstart listener that calls preventDefault, and enable Use mouse input on the Touch object.
You build a touch-driven puzzle game in Construct 3, test it in mobile Safari on an iPhone, and everything works. The publisher asks you to ship it as an installable PWA so players can launch it from the home screen with no browser chrome. You add a manifest, configure the icons, and test the install. The game launches full-screen and looks great — but taps no longer register. Sometimes a tap registers as a long press. Sometimes the second tap of a double-tap fails. The Touch object is doing its best, but iOS in standalone mode is silently cancelling your touch events to reserve gestures for itself.
The Symptom
The same Construct 3 export behaves differently when launched from Safari versus when installed as a PWA:
First tap ignored. The user taps a button. Nothing happens. The second tap on the same button works. The first tap appears to have been swallowed by the system as a possible swipe or zoom gesture.
Drag input cuts out mid-gesture. A drag starts, moves a few pixels, and stops firing On Touched Object. The system has decided the gesture is a swipe-from-edge or scroll attempt and cancelled the rest of the touch.
Buttons need a long press. Single taps do nothing, but holding for half a second triggers the action. The fast-tap path is being eaten by iOS’s 300 ms double-tap zoom check.
Works in Safari, broken when installed. The exact same URL behaves correctly in mobile Safari but breaks in standalone PWA mode. This is the giveaway that you are looking at a WKWebView gesture configuration difference, not a Construct 3 bug.
What Causes This
System gesture reservation. iOS reserves several gestures for itself: double-tap to zoom, swipe from the edge to navigate or open Control Center, and long-press for selection or callouts. To detect these, the OS waits a few hundred milliseconds before delivering touch events to the page, and may cancel them entirely with a pointercancel event if the system claims the gesture. Construct 3’s Touch object listens for the standard touch events — if those events get cancelled, no triggers fire.
Passive event listeners. Modern browsers default touch event listeners to passive mode, which means they cannot call preventDefault(). This is great for scroll performance but bad for games that need to claim touches before the system reroutes them. Construct 3’s runtime adds its own listeners but cannot always override the system defaults in PWA mode.
Missing touch-action CSS. The CSS touch-action property tells the browser which gestures the page wants to handle versus which it cedes to the system. Without an explicit value, the browser uses touch-action: auto, which permits all system gestures including pinch-zoom, double-tap-zoom, and edge swipes. Each of these can intercept your taps.
WKWebView vs Safari differences. Safari and standalone PWAs use the same WebKit engine but different host configurations. Safari’s host fine-tunes gesture handling for browsing; the standalone PWA host uses a more conservative default that assumes the page may not have been designed for fullscreen interaction. Bugs that exist only in PWA mode are common enough that Apple’s own developer docs warn about testing both contexts.
The Fix
Step 1: Set touch-action in your exported HTML. After exporting your Construct 3 project for HTML5, edit the generated index.html and add a style block (or extend an existing one) that sets touch-action: manipulation on the body and the C3 canvas. The manipulation value allows panning and pinch-zoom but disables the 300 ms double-tap delay, which removes the most painful tap-eating gesture on iOS.
// In your exported index.html, inside <head>
<style>
html, body {
touch-action: manipulation;
overscroll-behavior: none;
-webkit-user-select: none;
user-select: none;
}
/* Construct 3 wraps the canvas in a container */
#c3canvasdiv, canvas {
touch-action: none; /* none = give us everything */
}
</style>
Use touch-action: none on the canvas itself for game touches and touch-action: manipulation on the body to keep the page from accidentally consuming gestures from any HTML overlay you might add later (loading screen, ad scripts, etc.).
Step 2: Attach a non-passive touchstart listener via Browser execjs. Add a Construct 3 event on the “On start of layout” trigger that calls the Browser plugin’s Execute JavaScript action with the snippet below. This installs an explicit non-passive listener that calls preventDefault() for touches on the canvas, blocking the system from re-routing them.
// In Browser.Execute JavaScript on start of layout
const canvas = document.querySelector('canvas');
if (canvas) {
// passive: false is the critical bit
canvas.addEventListener('touchstart', function (e) {
// Only prevent default for taps inside the canvas
if (e.target === canvas) {
e.preventDefault();
}
}, { passive: false });
canvas.addEventListener('gesturestart', function (e) {
e.preventDefault(); // kill pinch-zoom on canvas
}, { passive: false });
}
Calling preventDefault() on touchstart tells iOS that your code is handling the gesture, which short-circuits the system’s gesture detection and stops the pointercancel from firing. Combined with the CSS touch-action, this is the belt-and-suspenders approach that works reliably in PWA mode.
Configure the Touch Object
In the Construct 3 editor, select your Touch object and confirm the Use mouse input property is set to Yes. This makes the Touch object fire on mouse events as well, which catches the synthesized click events that iOS sometimes generates instead of (or in addition to) the touch event sequence. With mouse input enabled, even if a touch gets cancelled and promoted to a click, your Touch triggers will still fire.
If your game uses On tap object, also test with On touched object. The tap trigger waits to confirm the gesture is a tap and not a drag, which adds latency that interacts badly with iOS gesture cancellation. On touched object fires immediately on touchstart, which is more reliable for fast-paced games. Pick the one that matches your game’s feel and stick with it.
Verifying the Fix with Remote Debug
The only way to confirm the fix is to inspect the actual events firing inside the installed PWA. Connect your iPhone to a Mac via USB, enable Web Inspector on the iPhone (Settings > Safari > Advanced > Web Inspector), and on the Mac open Safari, then Develop > [your iPhone name] > [your PWA name]. You will see a regular DevTools window attached to the running PWA.
Open the Console and tap your game. You should see touchstart, possibly touchmove, and touchend log entries when you instrument the Browser execjs to log them. If you see pointercancel events between touchstart and touchend, the system is still claiming gestures — widen your CSS touch-action rule, or check whether a parent element is overriding it back to auto.
The Web Inspector also shows the manifest, service worker state, and any Console errors from your runtime. PWA-only bugs frequently show up here as failed resource loads or service worker scope mismatches that do not appear in regular Safari.
“An installed PWA on iOS is not Safari with a different chrome. It is a different host with different defaults. If your game works in mobile Safari and breaks when installed, the bug is almost always touch-action or a passive listener.”
Related Issues
If your touch events fire but the coordinates are wrong on iOS, see Construct 3 Touch Pinch Zoom Wrong Center for viewport scaling and devicePixelRatio fixes. If basic touch is broken on Android as well, the cause is more likely a misconfigured Touch object than a platform-specific gesture issue.
touch-action: none on the canvas + a non-passive touchstart listener — that combination wins on iOS PWA every time.