Why might dragging SVG elements via TouchEvent on iPhone iOS be laggy?

I have created a web interface where the user can drag and drop SVG elements on screen. I am struggling with the performance of moving the SVGs via touch events on iPhone iOS using the webkit engine.

Everything is fine on desktop browsers and on Android phones that I could get hold of, but iOS on iPhones shows very bad performance (seems fine on iOS on one iPad that I could get hold of, but it sometimes leaves some traces of the SVG after moving).

There seems to be a delay before the touchstart event kicks in after touching the device and a delay before the touchend event is triggered after releasing the touch: An audio sample (already loaded) that is supposed to play after picking up or dropping the element plays with a delay of ~1.5 seconds. The touchmove event seems to be handled smoothly though – no delay with moving the SVG (after touchstart has ended).

I have already checked iOS Delay between touchstart and touchmove? – but the site that’s linked to doesn’t help me. I fail to get the scroll event on any element (window, document, svgElement) – and even if I did, I wouldn’t know how this could help me.

I assumed the the issue might be related to the size of the base64 encoded background image that the SVGs are using, but reduzing that size even dramatically didn’t help.

I read about some 300-350ms delay that iOS might have if there’s no “fast tap” mode set, but a) the delay between touching/releasing the screen and playing the audio is longer than 350ms (rather 1.5 seconds) and b) playing with the touch-action CSS property did not help. (Eliminate 300ms delay on click events in mobile Safari)

I am really not sure if I am doing anything wrong (very well possible!) or if the webkit engine on (iPhone) iOS is simply so bad (compared to e.g. Blink on Android that runs flawlessly) that it cannot handle to render/move SVGs? Testing this is particularly iffy, because Browserstack doesn’t issue TouchEvents properly and I never succeded to hook up the single physical iOS device that I have (a 2015 iPod Touch) to my Linux machine for remote debugging (while it’s very simple for Android on Chromium). I’d really be grateful for hints!

An SVG roughly follows the following pattern (some attributes like viewBox, stroke-width etc. omitted):

<svg>  
  <defs><pattern id="SOME_ID"><img href="data:SOME_BASE64_ENCODED_IMAGE" /></defs>  
  <path fill="url(#SOME_ID)" d="SOME_SIMPLE_PATH"></path>  
  <path d="SOME_OTHER_SIMPLE_PATH"></path>  
</svg>  

The SVGs can be moved by MouseEvent or TouchEvent using the following logic:

// this.svgElement is the DOM element within the class
this.svgElement.addEventListener('touchstart', this.handleMoveStarted, false);  
this.svgElement.addEventListener('mousedown', this.handleMoveStarted, false);  

// Keep track of start position and add move/end listeners
handleMoveStarted(event) {  
  event.preventDefault();  
  event.stopPropagation();  
  
  if (event.type === 'touchstart') {  
    this.moveInitialX = event.touches[0].clientX;  
    this.moveInitialY = event.touches[0].clientY;  
    this.svgElement.addEventListener('touchmove', this.handleMoved, false);
    this.svgElement.addEventListener('touchend', this.handleMoveEnded, false);
  }  
  else {
    // Same principle for event.clientX/Y and MouseEvent
  }
   
  // Callback to play audio here
}

// Compute delta position and update
handleMoved(event) {
  event.preventDefault();
  event.stopPropagation();

  let deltaX = 0;
  let deltaY = 0;

  if (event.type === 'touchmove') {
    deltaX = this.moveInitialX - event.touches[0].clientX;
    deltaY = this.moveInitialY - event.touches[0].clientY;
    this.moveInitialX = event.touches[0].clientX;
    this.moveInitialY = event.touches[0].clientY;
  }
  else {
    // Same principle for event.clientX/Y and MouseEvent
  }

  this.svgElement.style.left = `${parseFloat(this.svgElement.style.left) - deltaX}px`;
  this.svgElement.style.top = `${parseFloat(this.svgElement.style.top) - deltaY}px`;
}

// Used to remove listeners on tochenend/mouseup
handleMoveEnded(event) {
  event.preventDefault();
  event.stopPropagation();

  this.svgElement.removeEventListener('mousemove', this.handleMoved);
  this.svgElement.removeEventListener('touchmove', this.handleMoved);
  this.svgElement.removeEventListener('mouseup', this.handleMoveEnded);
  this.svgElement.removeEventListener('touchend', this.handleMoveEnded);

  // Callback to play audio here
}