Warning
This is just an example of how to put popups using a third party library. We do not
give support to Floating-UI
or any other third party library.
This example uses pure javascript along with the third party library Floating-UI to create a popup
that shows a thumbnail over available nodes.
It's important that your popups that are going to show thumbnails on their content, independently of which library
you use to do it, use a timeout to create the popup once the mouse has stopped moving over a node.
This will prevent downloading too many thumbnails when moving the mouse over the map, and just download the one
that you really are going to use.
Floating-UI has a method to autoUpdate
the positioning of the popup when some global events
like resizing or scrolling happens, But you would need to use zooming
and panning
triggers to track
also map changes.
If you end using Floating-UI autoUpdate
function, you can use
the MapViewerNode object as a virtual dom element, but you must
set instanced_nodes
flag to true
to make sure the MapViewerNode
object is updated.
Map Viewer
Code
| <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
.popup {
background-color: white;
color: black;
border: 1px solid black;
padding: 1px;
width: max-content;
position: absolute;
pointer-events: none;
text-align: center;
top: 0;
left: 0;
}
.popup.hidden {
visibility: hidden;
pointer-events: none;
}
.popup-thumb {
width: 150px;
height: 150px;
}
.popup img {
width: 100%;
}
</style>
</head>
<body>
<div id="viewer-container"></div>
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/core@1.6.9"></script>
<script src="https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.13"></script>
<script src="https://tk3d.tk3dapi.com/dvm/v1/lib/stable/dvm.js"></script>
<script>
const input_options = {
container: "viewer-container",
styles_by_groups: true,
instanced_nodes: true
};
// ---- LOADING MODULE ----
DVM.loadModule("map_viewer", input_options)
.then(function(viewer) {
window.viewer = viewer;
start(viewer);
})
.catch(function(err) {
console.error(err);
});
function start(viewer) {
// Just for the example purpose
viewer.flags.scroll_with_mod_key = true;
// Floating-UI use this global variable when using its UMD build
const { computePosition, autoUpdate, autoPlacement } = FloatingUIDOM;
const venue_id = "eu-gb-00021-football"
const map_id = "blockmap";
// Element that will do the popup functionality
const popup = document.createElement("div");
popup.id = "popup";
popup.className = "popup hidden";
popup.innerHTML = `<div class="popup-title"></div><div class="popup-thumb"></div>`;
const popup_title = popup.querySelector(".popup-title");
const popup_thumb = popup.querySelector(".popup-thumb");
let popup_node = null;
let popup_cleanup = null;
let popup_timeout = null;
viewer.subscribe("enter", (obj) => {
const node = obj.nodes[0];
if (node && (node.state === "available" || node.state === "selected")) {
setupPopup(node);
}
});
viewer.subscribe("leave", (_obj) => {
removePopup();
});
viewer.subscribe("zooming", (_obj) => {
updatePopup();
});
viewer.subscribe("panning", (_obj) => {
updatePopup();
});
viewer.subscribe("load_success", (_obj) => {
setRandomAvailability()
})
// Load the map
viewer.loadMap({ venue_id, map_id })
.then((_obj) => {
// Successfully loaded
console.log("LOADED!");
})
.catch((err) => {
// Error while loading
console.error(err);
});
function updatePopup() {
// Tracking changes on the map that may cause the node to go off-screen
if (popup_node && popup_node.on_screen) {
showPopup();
updatePopupPosition(popup_node);
} else {
hidePopup();
}
}
function updatePopupPosition(node) {
computePosition(node, popup, {
middleware: [autoPlacement()]
}).then(({ x, y }) => {
Object.assign(popup.style, {
left: `${x}px`,
top: `${y}px`
});
});
}
function setupFloatingUI(node) {
// Track scrolls, resizes and whatever floating-ui tracks
popup_cleanup = autoUpdate(node, popup, () => {
updatePopupPosition(node);
});
}
function clearFloatingUI() {
if (popup_cleanup) {
// Stop floating-ui tracking
popup_cleanup();
}
popup_cleanup = null;
}
function setupPopup(node) {
clearPopupTimeout();
popup_node = node;
popup_title.innerText = "Node: " + node.id;
// This setTimeout prevents making too many calls just moving the mouse over the map
popup_timeout = setTimeout(() => {
viewer.getThumbnail({venue_id, view_id: node.id }).then((thumb) => {
if (node === popup_node) {
popup_thumb.innerHTML = "";
popup_thumb.appendChild(thumb)
}
});
appendPopup();
setupFloatingUI(node);
showPopup();
}, 200);
}
function removePopup() {
clearPopupTimeout();
clearFloatingUI();
hidePopup();
popup_node = null;
popup_title.innerText = "";
popup_thumb.innerHTML = "";
if (popup.parentElement) {
popup.parentElement.removeChild(popup);
}
}
function clearPopupTimeout() {
if (popup_timeout) {
clearTimeout(popup_timeout);
popup_timeout = null;
}
}
function showPopup() {
popup.classList.remove("hidden");
}
function hidePopup() {
popup.classList.add("hidden");
}
function appendPopup() {
document.body.appendChild(popup);
}
// Just for the purpose of having some random availability on the map
function setRandomAvailability(prob = 0.6) {
if (viewer && viewer.isLoaded()) {
viewer.getTypesList().forEach((type) => {
const availability = [];
viewer.getNodesByType(type).forEach((node) => {
if (Math.random() < prob) {
availability.push(node.id);
}
});
viewer.setAvailability(type, availability);
});
}
}
}
</script>
</body>
</html>
|