Improve map UX: compact modal, visible crosshair, smart marker overlap
- Reduce create post modal height (max 45vh) and add scroll - Lower modal position (bottom: 8%) to keep map visible - Move crosshair higher (top: 30%) for better visibility - Implement smart marker offsetting for nearby posts - Add visual connection line from offset markers to true position - Create cascade effect for clustered markers (45° circular pattern) - Maintain pointer accuracy when zooming 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c4c9a7065
commit
c3e9426e64
|
|
@ -294,6 +294,50 @@ function openCenteredOverlay({ map, post, theme }) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateSmartOffset(post, markersRef, map) {
|
||||||
|
if (!map || !markersRef.current) return { x: 0, y: 0 };
|
||||||
|
|
||||||
|
const lat = typeof post.lat === "number" ? post.lat : typeof post.latitude === "number" ? post.latitude : null;
|
||||||
|
const lon = typeof post.lon === "number" ? post.lon : typeof post.lng === "number" ? post.lng : null;
|
||||||
|
if (lat == null || lon == null) return { x: 0, y: 0 };
|
||||||
|
|
||||||
|
const currentPoint = map.project([lon, lat]);
|
||||||
|
const PROXIMITY_THRESHOLD = 80; // pixels
|
||||||
|
const MAX_OFFSET = 60; // max offset in pixels
|
||||||
|
|
||||||
|
const nearbyMarkers = [];
|
||||||
|
|
||||||
|
for (const marker of markersRef.current) {
|
||||||
|
if (!marker.post || marker.post.id === post.id) continue;
|
||||||
|
|
||||||
|
const mLat = typeof marker.post.lat === "number" ? marker.post.lat : marker.post.latitude;
|
||||||
|
const mLon = typeof marker.post.lon === "number" ? marker.post.lon : marker.post.lng;
|
||||||
|
if (mLat == null || mLon == null) continue;
|
||||||
|
|
||||||
|
const mPoint = map.project([mLon, mLat]);
|
||||||
|
const dist = Math.sqrt(
|
||||||
|
Math.pow(currentPoint.x - mPoint.x, 2) +
|
||||||
|
Math.pow(currentPoint.y - mPoint.y, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dist < PROXIMITY_THRESHOLD) {
|
||||||
|
nearbyMarkers.push({ marker, distance: dist, point: mPoint });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nearbyMarkers.length === 0) return { x: 0, y: 0 };
|
||||||
|
|
||||||
|
// Create a cascade effect: offset markers in a circular pattern
|
||||||
|
const index = nearbyMarkers.length;
|
||||||
|
const angle = (index * 45) * (Math.PI / 180); // 45 degrees apart
|
||||||
|
const offsetMagnitude = Math.min(20 + (index * 12), MAX_OFFSET);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: Math.cos(angle) * offsetMagnitude,
|
||||||
|
y: Math.sin(angle) * offsetMagnitude
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
|
export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, theme = "blue") {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
@ -316,6 +360,9 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
|
|
||||||
const lngLat = [lon, lat];
|
const lngLat = [lon, lat];
|
||||||
|
|
||||||
|
// Calculate smart offset for nearby markers
|
||||||
|
const offset = calculateSmartOffset(post, markersRef, map);
|
||||||
|
|
||||||
const root = document.createElement("div");
|
const root = document.createElement("div");
|
||||||
root.classList.add("sw-appear");
|
root.classList.add("sw-appear");
|
||||||
requestAnimationFrame(() => root.classList.add("sw-appear-in"));
|
requestAnimationFrame(() => root.classList.add("sw-appear-in"));
|
||||||
|
|
@ -334,9 +381,19 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
root.className = "post-pin post-pin--compact sw-appear sw-appear-in";
|
root.className = "post-pin post-pin--compact sw-appear sw-appear-in";
|
||||||
root.style.zIndex = "1";
|
root.style.zIndex = "1";
|
||||||
|
|
||||||
|
// Add connecting line if offset is significant
|
||||||
|
const hasOffset = Math.abs(offset.x) > 5 || Math.abs(offset.y) > 5;
|
||||||
|
const lineHTML = hasOffset
|
||||||
|
? `<svg class="sw-offset-line" style="position:absolute;bottom:0;left:50%;transform:translateX(-50%);overflow:visible;pointer-events:none;z-index:-1;">
|
||||||
|
<line x1="0" y1="0" x2="${-offset.x}" y2="${-offset.y}" stroke="rgba(56,189,248,0.4)" stroke-width="2" stroke-dasharray="4,4"/>
|
||||||
|
<circle cx="${-offset.x}" cy="${-offset.y}" r="4" fill="#38bdf8" opacity="0.6"/>
|
||||||
|
</svg>`
|
||||||
|
: '';
|
||||||
|
|
||||||
root.innerHTML = `
|
root.innerHTML = `
|
||||||
<div class="sw-template-mini-wrap"></div>
|
<div class="sw-template-mini-wrap"></div>
|
||||||
<div class="post-pin-pointer-small"></div>
|
<div class="post-pin-pointer-small"></div>
|
||||||
|
${lineHTML}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const wrap = root.querySelector(".sw-template-mini-wrap");
|
const wrap = root.querySelector(".sw-template-mini-wrap");
|
||||||
|
|
@ -352,7 +409,11 @@ export function createMarkerForPost(post, mapRef, markersRef, expandedElRef, the
|
||||||
renderCompact();
|
renderCompact();
|
||||||
root.__renderCompact = renderCompact;
|
root.__renderCompact = renderCompact;
|
||||||
|
|
||||||
const marker = new maplibregl.Marker({ element: root, anchor: "bottom" })
|
const marker = new maplibregl.Marker({
|
||||||
|
element: root,
|
||||||
|
anchor: "bottom",
|
||||||
|
offset: [offset.x, offset.y]
|
||||||
|
})
|
||||||
.setLngLat(lngLat)
|
.setLngLat(lngLat)
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@
|
||||||
/* Crosshair pour création */
|
/* Crosshair pour création */
|
||||||
.map-crosshair{
|
.map-crosshair{
|
||||||
left:50%;
|
left:50%;
|
||||||
top:40%;
|
top:30%;
|
||||||
transform:translate(-50%,-50%);
|
transform:translate(-50%,-50%);
|
||||||
}
|
}
|
||||||
.crosshair-aim{
|
.crosshair-aim{
|
||||||
|
|
@ -141,10 +141,12 @@
|
||||||
/* Panneau de création */
|
/* Panneau de création */
|
||||||
.create-post-panel{
|
.create-post-panel{
|
||||||
left:50%;
|
left:50%;
|
||||||
bottom:20%;
|
bottom:8%;
|
||||||
transform:translateX(-50%);
|
transform:translateX(-50%);
|
||||||
width:92%;
|
width:92%;
|
||||||
max-width:520px;
|
max-width:520px;
|
||||||
|
max-height:45vh;
|
||||||
|
overflow-y:auto;
|
||||||
background: rgba(15, 23, 42, 0.96);
|
background: rgba(15, 23, 42, 0.96);
|
||||||
border-radius:16px;
|
border-radius:16px;
|
||||||
border:1px solid rgba(148, 163, 184, 0.9);
|
border:1px solid rgba(148, 163, 184, 0.9);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue