offsetX/Y + matrix3d = bad
When hovering over a Slot, the offsetX/Y of the 'pointermove' event becomes relative to the slot, regardless of where the event is listened from! This is easy to fix in a flat app, but more complex in a transformed one!
by David Iglesias
December 14, 2022
HTML
<div class="transform">
<div class="app">
<div class="custom-content" slot="content">
Hover over this, <tt>(x, y)</tt> will reset to 0,0!
</div>
</div>
</div>
<!-- Force scroll and render debug info -->
<div class="far">
And here, some content so the above can scroll!
</div>
<div id="output">x: ?<br />y: ?</div>
CSS
* { box-sizing: border-box; }
body { font: 14px sans-serif; }
.transform {
transform: matrix3d(0.498701, -0.26373, 0, 0.000148,
0.303401, 0.323287, 0, -0.000143,
0, 0, 1, 0,
20, 20, 0, 1);
}
.app {
background: #fbd;
width: 320px;
height: 200px;
border: 1px solid black;
position: relative;
cursor: move;
transform-style: preserve-3d;
}
.custom-content {
display: block;
border: 1px solid black;
background: #dbf;
width: 100px;
height: 100px;
position: absolute;
top: 50%; margin-top: -50px;
left: 50%; margin-left: -50px;
padding: 5px;
cursor: crosshair;
transform: perspective(500px) rotateY(45deg) translateZ(30px);
}
#output {
font-family: monospace;
background: #eee;
position: fixed;
bottom: 0; left: 0; right: 0;
border-top: 1px solid #ddd;
}
.active {
border-color: red;
}
.far {
margin-top: 500px;
}
.fixed {
text-decoration: line-through;
}
JavaScript
function computeCoordinates(event) {
// event.offsetX/Y are "wrong" for slots
let x = event.offsetX;
let y = event.offsetY;
return {x: x, y: y};
}
// Setup the app
var app = document.querySelector('.app');
let shadowRoot = app.attachShadow({
mode: 'open',
});
let slot = document.createElement('slot');
slot.name = 'content';
shadowRoot.appendChild(slot);
// Event handlers
app.addEventListener('pointermove', function(event) {
let coords = computeCoordinates(event);
updateOutput(coords);
}, { capture: false });
app.addEventListener('pointerleave', function(event) {
event.target.classList.remove('active');
}, { capture: true });
app.addEventListener('pointerenter', function(event) {
event.target.classList.add('active');
}, { capture: true });
function updateOutput(info) {
output.innerText = `x: ${Math.round(info.x)}\ny: ${Math.round(info.y)}`;
}