In the following example, dragging the cube to another grid cell works the first time or two. But if you keep trying to let go, and then drag the cube to a new cell, you'll find sometimes it doesn't work.
You will notice, that when it doesn't work, the document
's pointerup
event does not fire as we expect it to, and once we let go of the pointer, then the cube keeps on moving even when we are not dragging.
The code that sets up the event handlers starts down on line 159 of the JS. Make it full screen on desktop, it may not fit a mobile screen well. Don't worry about what the custom elements do, the main issue is that the event handlers at the end don't seem to work as intended.
(video after the example)
EDIT: This particular issue seems to happen only in Chromium (desktop), but I don't experience the issue in Firefox (desktop). Maybe this is a Chrome bug.
{
// Register the LUME HTML elements with the browser.
LUME.useDefaultNames();
const { Node, html, reactify, autorun } = LUME;
class Cube extends Node {
constructor(...args) {
super(...args);
reactify(this, ['cubeSize'])
}
cubeSize = 200;
connectedCallback() {
super.connectedCallback();
// Keep it a cube.
this.stop = autorun(() => {
this.size = [this.cubeSize, this.cubeSize, this.cubeSize];
});
}
disconnectedCallback() {
super.disconnectedCallback();
this.stop();
}
// prettier-ignore
template = () => html`
<lume-node size="0 0 0" align="0.5 0.5 0.5">
<lume-node class="cubeFace" position=${() => [0, 0, this.cubeSize/2]} rotation="0 0 0" size=${() => [this.cubeSize, this.cubeSize, 0]} mount-point="0.5 0.5" align="0.5 0.5 0.5"></lume-node>
<lume-node class="cubeFace" position=${() => [0, 0, -this.cubeSize/2]} rotation="0 180 0" size=${() => [this.cubeSize, this.cubeSize, 0]} mount-point="0.5 0.5" align="0.5 0.5 0.5"></lume-node>
<lume-node class="cubeFace" position=${() => [-this.cubeSize/2, 0, 0]} rotation="0 -90 0" size=${() => [this.cubeSize, this.cubeSize, 0]} mount-point="0.5 0.5" align="0.5 0.5 0.5"></lume-node>
<lume-node class="cubeFace" position=${() => [this.cubeSize/2, 0, 0]} rotation="0 90 0" size=${() => [this.cubeSize, this.cubeSize, 0]} mount-point="0.5 0.5" align="0.5 0.5 0.5"></lume-node>
<lume-node class="cubeFace" position=${() => [0, -this.cubeSize/2, 0]} rotation="-90 0 0" size=${() => [this.cubeSize, this.cubeSize, 0]} mount-point="0.5 0.5" align="0.5 0.5 0.5"></lume-node>
<lume-node class="cubeFace" position=${() => [0, this.cubeSize/2, 0]} rotation="90 0 0" size=${() => [this.cubeSize, this.cubeSize, 0]} mount-point="0.5 0.5" align="0.5 0.5 0.5"></lume-node>
</lume-node>
`
css = `
.cubeFace {
background: linear-gradient(43deg, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%);
}
`;
}
customElements.define('cube-', Cube)
/**
* @mixin
* @class ChildWatcher -
* A mixin class that gives your custom element a childrenChagnedCallback()
* that runs any time your element's children have changed while your element
* is connected into a live DOM tree.
*/
function ChildWatcher(Base) {
return class ChildWatcher extends Base {
connectedCallback() {
super.connectedCallback?.();
// When children have changed, recompute the layout.
this.observer = new MutationObserver(() =>
this.childrenChangedCallback?.()
);
this.observer.observe(this, { childList: true });
}
disconnectedCallback() {
super.disconnectedCallback?.();
this.observer.disconnect();
}
};
}
class GridLayout extends ChildWatcher(LUME.Node) {
constructor(...args) {
super(...args)
reactify(this, ['rows', 'columns'])
}
// If rows or columns is not provided (both zero), then the default is a square grid
// that fits all images (f.e. give 7 images, the grid rows and columns
// would be 3 and 3, for a total of 9 cells, and two cells would be empty
// in the last row).
rows = 0;
columns = 0;
connectedCallback() {
super.connectedCallback();
// Run an initial layout on connect, and also recompute layout whenever this.rows or this.columns change.
this.stop = LUME.autorun(() => {
this.layout(this.rows, this.columns);
});
}
disconnectedCallback() {
// Don't forget cleanup!
this.stop();
}
childrenChangedCallback() {
// Recompute layout any time this element's children have changed
// (it is batched, so happens once per macrotask, for better performance)
this.layout(this.rows, this.columns);
}
layout(rows, columns) {
// Calculate the grid rows and columns to be a square if rows/columns isn't provided.
if (!rows || !columns) {
const size = Math.ceil(Math.sqrt(this.children.length));
rows = size;
columns = size;
}
const cells = rows * columns;
const gridSize = this.calculatedSize; // [x, y, z] in px units
const cellWidth = gridSize.x / columns;
const cellHeight = gridSize.y / rows;
for (let i = 0; i < cells; i += 1) {
const node = this.children[i];
// If we have less nodes than total cells, quit early.
if (!node) break;
node.size = [cellWidth, cellHeight];
node.position = {
x: (i % columns) * cellWidth,
y: Math.floor(i / columns) * cellHeight
};
}
}
}
customElements.define('grid-layout', GridLayout)
}
const cells = document.querySelectorAll("grid-layout > *");
// This event never fires
cells.forEach((n, i) => {
n.addEventListener("gotpointercapture", (event) => {
console.log(" --------------- pointer capture, uncapture it.", event.target);
event.target.releasePointerCapture(event.pointerId);
});
});
// This event also never fires
gridContainer.addEventListener("gotpointercapture", (event) => {
console.log(" --------------- pointer capture, uncapture it.", event.target);
event.target.releasePointerCapture(event.pointerId);
});
cube.addEventListener("pointerdown", (event) => {
console.log(" --------------- pointer down, start things.");
event.target.releasePointerCapture(event.pointerId);
cube.classList.add("no-events");
const handlers = [];
cells.forEach((n, i) => {
const handler = (event) => {
console.log(" --------------- pointer over cell, move cube.");
event.target.releasePointerCapture(event.pointerId);
console.log("pointer over cell", i);
cube.position = n.position;
};
handlers.push(handler);
n.addEventListener("pointerover", handler);
});
document.addEventListener(
"pointerup",
(event) => {
console.log(" --------------- pointer up, stop things.");
cube.classList.remove("no-events");
cells.forEach((n, i) => n.removeEventListener("pointerover", handlers[i]));
},
{ once: true }
);
});
html,
body {
width: 100%;
height: 100%;
margin: 0;
background: #444;
}
grid-layout {
outline: 4px solid #fc466b;
touch-action: none;
pointer-events: none;
}
grid-layout > * {
outline: 1px solid #3f5efb;
pointer-events: auto;
}
.no-events {
pointer-events: none;
}
<!--
Made with LUME
http://github.com/lume/lume
-->
<script src="https://assets.codepen.io/191583/LUME.unversioned.2.js"></script>
<lume-scene id="scene" perspective="1000">
<lume-node id="gridContainer" size="600 600" position="20 20" align="0.5 0.5" mount-point="0.5 0.5" rotation="10 30 0">
<cube- class="Xno-events" id="cube" cubeSize="200" position="0 0"></cube->
<!-- Make the grid fill 75% width and 75% height of the scene, and center it. -->
<grid-layout id="grid" size-mode="proportional proportional" size="1 1">
<!-- Put nine cells in the grid, so 3x3 grid by default -->
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
<lume-node></lume-node>
</grid-layout>
</lume-node>
</lume-scene>
The video on youtube shows that the initial drag works well. After I let go of the mouse, I wiggle the mouse to signal when I've finished dragging.
Then I try to drag a second time, but the cube doesn't move. Once I've let go, and I am no longer holding the mouse button, the cube starts to move (it should not move if the mouse button is not down).
The reason the cube moves when I am not dragging is because the document
's pointerup
event didn't fire when I last stopped dragging, so it did not remove the pointerover
events on the cells.
While I was trying to drag the cube the second time, none of the cell pointerover
events were firing while I was trying to drag (due to the aforementioned problem). Those events only started firing after I let go of the mouse button, but this is wrong and when I am not dragging there should be no movement of the cube.