feat: support custom HTML/SVG element markers in MapLibre
All checks were successful
CI / Build and Test (push) Successful in 37s
CI / Build and Test (pull_request) Successful in 33s

Add Element, Anchor, and Rotation fields to the Marker struct so users
can render custom icons (ships, circles, buildings) instead of the
default pin. When Element is set, Color is ignored and the raw HTML/SVG
is injected as the marker's DOM element.

Update the example to demonstrate a static ship SVG marker with rotation
and a signal-driven vehicle marker using a custom SVG circle.
This commit is contained in:
Ryan Hamamura
2026-02-20 10:38:12 -10:00
parent cbc5022e0d
commit e6dc8c27fd
3 changed files with 47 additions and 3 deletions

View File

@@ -75,14 +75,30 @@ func main() {
}, },
}) })
// Purple vehicle marker — reads shared Go state // Custom SVG ship marker (static)
m.AddMarker("ship", maplibre.Marker{
LngLat: maplibre.LngLat{Lng: -122.38, Lat: 37.80},
Element: `<svg width="32" height="32" viewBox="0 0 24 24" fill="#1a5276" xmlns="http://www.w3.org/2000/svg">` +
`<path d="M3 17h18l-3-8H6L3 17zm9-14l-2 4h4l-2-4zM1 19h22v2H1v-2z"/>` +
`</svg>`,
Anchor: "center",
Rotation: 45,
Popup: &maplibre.Popup{
Content: "<strong>Ferry</strong><p>Heading NE</p>",
},
})
// Custom SVG vehicle marker — reads shared Go state
vehicleLng := c.Signal(-122.43) vehicleLng := c.Signal(-122.43)
vehicleLat := c.Signal(37.77) vehicleLat := c.Signal(37.77)
m.AddMarker("vehicle", maplibre.Marker{ m.AddMarker("vehicle", maplibre.Marker{
LngSignal: vehicleLng, LngSignal: vehicleLng,
LatSignal: vehicleLat, LatSignal: vehicleLat,
Color: "#9b59b6", Element: `<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">` +
`<circle cx="10" cy="10" r="9" fill="#9b59b6" stroke="#fff" stroke-width="2"/>` +
`</svg>`,
Anchor: "center",
}) })
c.OnInterval(time.Second, func() { c.OnInterval(time.Second, func() {

View File

@@ -179,10 +179,25 @@ func initScript(m *Map) string {
// markerBodyJS generates JS to add a marker, assuming `map` is in scope. // markerBodyJS generates JS to add a marker, assuming `map` is in scope.
func markerBodyJS(mapID, markerID string, mk Marker) string { func markerBodyJS(mapID, markerID string, mk Marker) string {
var b strings.Builder var b strings.Builder
if mk.Element != "" {
b.WriteString(fmt.Sprintf(
`var _mkEl=document.createElement('div');_mkEl.innerHTML=%s;`,
jsonStr(mk.Element)))
}
opts := "{" opts := "{"
if mk.Color != "" { if mk.Element != "" {
opts += `element:_mkEl.firstElementChild||_mkEl,`
} else if mk.Color != "" {
opts += fmt.Sprintf(`color:%s,`, jsonStr(mk.Color)) opts += fmt.Sprintf(`color:%s,`, jsonStr(mk.Color))
} }
if mk.Anchor != "" {
opts += fmt.Sprintf(`anchor:%s,`, jsonStr(mk.Anchor))
}
if mk.Rotation != 0 {
opts += fmt.Sprintf(`rotation:%s,`, formatFloat(mk.Rotation))
}
if mk.Draggable { if mk.Draggable {
opts += `draggable:true,` opts += `draggable:true,`
} }

View File

@@ -302,6 +302,19 @@ type Marker struct {
Draggable bool Draggable bool
Popup *Popup Popup *Popup
// Element is raw HTML/SVG used as a custom marker instead of the
// default pin. When set, Color is ignored.
// Do not pass untrusted user input without sanitizing it first.
Element string
// Anchor controls which part of the element sits at the coordinate.
// Values: "center" (default for custom elements), "bottom" (default
// for the pin), "top", "left", "right", "top-left", etc.
Anchor string
// Rotation is clockwise degrees. Useful for directional icons (ships, vehicles).
Rotation float64
// Signal-backed position. When set, signals drive marker position reactively. // Signal-backed position. When set, signals drive marker position reactively.
// Initial position is read from the signal values. LngLat is ignored when signals are set. // Initial position is read from the signal values. LngLat is ignored when signals are set.
// If Draggable is true, drag updates write back to these signals. // If Draggable is true, drag updates write back to these signals.