Answering my own question. One workaround is to clone and temporarily insert the dragged element into the DOM, and then set that as the drag image on the event data transfer object. This also has the benefit of the drag image being positioned correctly when specifying the X and Y coordinates, unlike the natively inserted drag image, which doesn't take the transform of the ancestor into account.
Here's an updated Codepen example. https://codepen.io/Veikko-Lehmuskorpi/pen/oggKRZv
const source = document.querySelector(".item");
let ghostEl;
source.addEventListener("dragstart", (event) => {
ghostEl = event.target.cloneNode(true);
ghostEl.classList.add("ghost");
document.body.appendChild(ghostEl);
event.dataTransfer.setDragImage(ghostEl, event.offsetX, event.offsetY);
});
source.addEventListener("dragend", () => {
ghostEl.remove();
});
body {
margin: 0;
}
.container {
width: 100vw;
height: 100vh;
background: #ccc;
/* This breaks dragging the child item on Safari, unless a custom drag image is set */
transform: scale(0.5);
}
.item {
background: #ddd;
width: 300px;
height: 150px;
font-size: 72px;
}
.ghost {
position: absolute;
top: -99999px;
left: -99999px;
}
<div class="container">
<div class="item" draggable="true">
draggable
</div>
</div>