Restyle the maps after mapcn and add a 3D globe demo

This commit is contained in:
codecalm committed 2026-10-04 17:20:02 +02:00
1 parent 9af4173f86
commit 9fae876516
9 files changed
+243 -86

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
---
"@tabler/core": minor
"@tabler/preview": minor
"@tabler/docs": patch
---
Updated the map look: gray basemap, dot markers, popups with no arrow, `.map-tooltip`, `.map-marker-label`, and a 3D globe demo.
+1 -1
View File
@@ -1281,7 +1281,7 @@ $ribbon-bookmark-notch-width: 0.5rem !default;
// scss-docs-start map-marker-variables
$map-marker-size: 1.75rem !default;
$map-marker-dot-size: 0.875rem !default;
$map-marker-dot-size: 1rem !default;
$map-marker-bg: var(--primary) !default;
$map-marker-color: var(--primary-fg) !default;
$map-marker-border-width: 2px !default;
@@ -52,6 +52,7 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre
"--tblr-border-color-translucent",
"--tblr-border-radius",
"--tblr-border-radius-pill",
"--tblr-border-radius-sm",
"--tblr-border-style",
"--tblr-border-width",
"--tblr-card-bg",
+40 -33
View File
@@ -65,9 +65,9 @@
border-color: var(--body-color);
}
// Popup
.maplibregl-popup-content {
padding: 0.75rem 1rem;
// Popup: a small bordered panel with no arrow
.maplibregl-popup .maplibregl-popup-content {
padding: 0.75rem;
color: var(--body-color);
background: var(--bg-surface);
box-shadow:
@@ -76,21 +76,25 @@
@include border-radius(var(--border-radius));
}
.maplibregl-popup-tip {
display: none;
}
// Room for the close button, which MapLibre pins to the right edge in both directions
.maplibregl-popup-content:has(.maplibregl-popup-close-button) {
.maplibregl-popup .maplibregl-popup-content:has(.maplibregl-popup-close-button) {
/* rtl:ignore */
padding-right: 2.25rem;
padding-right: 2rem;
}
.maplibregl-popup-close-button {
width: 1.5rem;
height: 1.5rem;
width: 1.25rem;
height: 1.25rem;
padding: 0;
margin: 0.375rem;
margin: 0.25rem;
font-size: 1rem;
line-height: 1;
color: var(--secondary);
@include border-radius(var(--border-radius));
@include border-radius(var(--border-radius-sm));
&:hover {
color: var(--body-color);
@@ -98,32 +102,17 @@
}
}
.maplibregl-popup-anchor-top,
.maplibregl-popup-anchor-top-left,
.maplibregl-popup-anchor-top-right {
.maplibregl-popup-tip {
border-bottom-color: var(--bg-surface);
}
}
.maplibregl-popup-anchor-bottom,
.maplibregl-popup-anchor-bottom-left,
.maplibregl-popup-anchor-bottom-right {
.maplibregl-popup-tip {
border-top-color: var(--bg-surface);
}
}
.maplibregl-popup-anchor-left .maplibregl-popup-tip {
border-right-color: var(--bg-surface);
}
.maplibregl-popup-anchor-right .maplibregl-popup-tip {
border-left-color: var(--bg-surface);
// Popup used as a tooltip: a short inverted label, shown on hover
.map-tooltip .maplibregl-popup-content {
padding: 0.25rem 0.5rem;
color: var(--text-inverted);
pointer-events: none;
background: var(--bg-surface-inverted);
box-shadow: #{$box-shadow-md};
}
// Popup that holds a card-like block: an image on top, padded content below
.map-popup-card {
.map-popup-card.maplibregl-popup {
.maplibregl-popup-content {
width: #{$map-popup-card-width};
padding: 0;
@@ -161,7 +150,6 @@
line-height: 1;
color: var(--map-marker-color);
cursor: pointer;
filter: #{$map-marker-shadow};
&::before {
position: absolute;
@@ -169,6 +157,7 @@
z-index: -1;
content: '';
background: var(--map-marker-bg);
filter: #{$map-marker-shadow};
border: var(--map-marker-border-width) solid var(--map-marker-border-color);
@include border-radius(var(--border-radius-pill));
}
@@ -187,6 +176,24 @@
--map-marker-size: #{$map-marker-dot-size};
}
// Short text under a marker
.map-marker-label {
position: absolute;
top: 100%;
left: 50%;
margin-top: 0.125rem;
font-size: $h6-font-size;
font-weight: var(--font-weight-medium);
line-height: 1.2;
color: var(--body-color);
text-shadow:
0 0 2px var(--bg-surface),
0 0 2px var(--bg-surface),
0 0 4px var(--bg-surface);
white-space: nowrap;
transform: translateX(-50%);
}
// Teardrop: a square with one sharp corner, turned to point down. Its tip
// reaches 20.7% below the circle, so the root is that much taller.
.map-marker-pin {
+71 -16
View File
@@ -12,6 +12,9 @@ classnames:
desc: Wrapper that keeps the map at a fixed aspect ratio
- class: map-marker
desc: Marker drawn in HTML, a circle that can hold an icon, a count or an avatar
part:
- class: map-marker-label
desc: Short text under a marker
modifier:
- class: map-marker-dot
desc: Small dot with no content
@@ -19,6 +22,8 @@ classnames:
desc: Teardrop pin that points down, used with the bottom anchor
- class: map-popup-card
desc: Popup with no padding and a fixed width, for an image above the text
- class: map-tooltip
desc: Popup shown as a small dark label, for text on hover
color:
- class: map-marker-{color}
desc: Any theme color, for example map-marker-red or map-marker-green
@@ -83,7 +88,7 @@ Create the map after the DOM is ready. Pass the id of your container, a style, a
document.addEventListener('DOMContentLoaded', function () {
const map = new maplibregl.Map({
container: 'map-simple',
style: 'https://tiles.openfreemap.org/styles/liberty',
style: 'https://tiles.openfreemap.org/styles/positron',
zoom: 13,
center: [13.4049, 52.518827],
})
@@ -102,10 +107,10 @@ Set `style` to the URL of any MapLibre style. OpenFreeMap has five of them:
| Style | Result |
| --- | --- |
| `https://tiles.openfreemap.org/styles/liberty` | Default street map. |
| `https://tiles.openfreemap.org/styles/positron` | Light gray map, used by the Tabler demos. Markers and data stand out on it. |
| `https://tiles.openfreemap.org/styles/dark` | Dark gray map, used by the demos in dark mode. |
| `https://tiles.openfreemap.org/styles/liberty` | Street map in color. |
| `https://tiles.openfreemap.org/styles/bright` | Street map with stronger colors. |
| `https://tiles.openfreemap.org/styles/positron` | Light gray map, good under charts and markers. |
| `https://tiles.openfreemap.org/styles/dark` | Dark map. |
| `https://tiles.openfreemap.org/styles/fiord` | Dark blue map. |
<Example>
@@ -116,7 +121,7 @@ Styles from other providers work the same way, as long as they follow the MapLib
### Markers
Add a marker for each place. `setLngLat` takes the same longitude and latitude order as `center`.
Add a marker for each place. `setLngLat` takes the same longitude and latitude order as `center`. The demos use a small dot, drawn with the `.map-marker` and `.map-marker-dot` classes.
```js
const cities = [
@@ -126,7 +131,10 @@ const cities = [
]
cities.forEach(function (coords) {
new maplibregl.Marker({ color: 'var(--tblr-primary)' }).setLngLat(coords).addTo(map)
const element = document.createElement('span')
element.className = 'map-marker map-marker-dot'
new maplibregl.Marker({ element: element }).setLngLat(coords).addTo(map)
})
```
@@ -134,7 +142,7 @@ cities.forEach(function (coords) {
<Map mapId="markers" />
</Example>
The `color` option is written to the `fill` attribute of the marker icon. A CSS variable works there, so `var(--tblr-primary)` keeps the markers in your theme color, including after a theme change.
Without `element`, MapLibre draws its own pin. Its `color` option accepts a CSS variable, for example `var(--tblr-primary)`.
### Custom markers
@@ -148,6 +156,8 @@ element.textContent = '12'
new maplibregl.Marker({ element: element }).setLngLat([13.35, 52.512]).addTo(map)
```
Put a `.map-marker-label` element inside the marker for a short text under it.
Add `.map-marker-dot` for a small dot, or `.map-marker-pin` for a pin that points down. A pin needs `anchor: 'bottom'`, so its tip sits on the place. Change the color with `.map-marker-{color}`.
```js
@@ -161,7 +171,7 @@ new maplibregl.Marker({ element: pin, anchor: 'bottom' }).setLngLat([13.47, 52.5
<Map mapId="marker-styles" title="Marker styles" center={[52.522, 13.405]} zoom={11}>
<div data-lng="13.345" data-lat="52.54"><span class="map-marker map-marker-dot"></span></div>
<div data-lng="13.39" data-lat="52.545"><span class="map-marker map-marker-dot map-marker-red"></span></div>
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /></span></div>
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /><span class="map-marker-label">Store</span></span></div>
<div data-lng="13.35" data-lat="52.512"><span class="map-marker map-marker-green">12</span></div>
<div data-lng="13.405" data-lat="52.522" data-anchor="bottom"><span class="map-marker map-marker-pin"><Icon name="home" /></span></div>
<div data-lng="13.47" data-lat="52.518" data-anchor="bottom"><span class="map-marker map-marker-pin map-marker-red"><Icon name="star" /></span></div>
@@ -201,15 +211,32 @@ marker.on('dragend', function () {
### Popups
Attach a popup to a marker with `setPopup`. It opens when the marker is clicked. Click a marker on the map above to see one.
Attach a popup to a marker with `setPopup`. It opens when the marker is clicked. Click a marker on the map above to see one. Tabler draws the popup as a small bordered panel with no arrow.
```js
new maplibregl.Marker({ color: 'var(--tblr-primary)' })
new maplibregl.Marker({ element: element })
.setLngLat([16.366667, 48.2])
.setPopup(new maplibregl.Popup({ offset: 32 }).setText('Vienna'))
.setPopup(new maplibregl.Popup({ offset: 16, closeButton: false }).setText('Vienna'))
.addTo(map)
```
Leave out `closeButton: false` to show a close button in the corner. The popup also closes on a click on the map.
### Tooltips
For a short label on hover, add the `map-tooltip` class to a popup. It turns the popup into a small dark label.
```js
const tooltip = new maplibregl.Popup({ closeButton: false, offset: 12, className: 'map-tooltip' })
map.on('mousemove', 'cities', function (event) {
tooltip.setLngLat(event.lngLat).setText(event.features[0].properties.name).addTo(map)
})
map.on('mouseleave', 'cities', function () {
tooltip.remove()
})
```
Use `setHTML` instead of `setText` for richer content. Only pass HTML you trust, as it is not escaped.
### Popup card
@@ -267,7 +294,7 @@ A map style does not change with the page theme on its own. Pick the style from
```js
const themeStyle = function () {
const dark = document.documentElement.getAttribute('data-bs-theme') === 'dark'
return 'https://tiles.openfreemap.org/styles/' + (dark ? 'dark' : 'liberty')
return 'https://tiles.openfreemap.org/styles/' + (dark ? 'dark' : 'positron')
}
const map = new maplibregl.Map({
@@ -299,7 +326,7 @@ map.on('style.load', function () {
type: 'line',
source: 'route',
layout: { 'line-cap': 'round', 'line-join': 'round' },
paint: { 'line-color': '#066fd1', 'line-width': 4 },
paint: { 'line-color': '#066fd1', 'line-width': 3, 'line-opacity': 0.8 },
})
})
```
@@ -330,7 +357,7 @@ function mapColor(name) {
return 'rgb(' + pixel[0] + ', ' + pixel[1] + ', ' + pixel[2] + ')'
}
map.addLayer({ id: 'route', type: 'line', source: 'route', paint: { 'line-color': mapColor('--tblr-primary'), 'line-width': 4 } })
map.addLayer({ id: 'route', type: 'line', source: 'route', paint: { 'line-color': mapColor('--tblr-primary'), 'line-width': 3 } })
```
Call it inside `style.load`, so the colors are read again after a theme change.
@@ -378,12 +405,40 @@ map.addLayer({
source: 'cities',
paint: {
'circle-color': '#066fd1',
'circle-opacity': 0.4,
'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 28],
'circle-opacity': 0.6,
'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 24],
},
})
```
### Content over the map
To put buttons or a small card over the map, place them next to the map container and position them with the utility classes:
```html
<div class="ratio ratio-16x9">
<div>
<div id="map-cities" class="w-100 h-100 rounded"></div>
<div class="btn-list position-absolute top-0 start-0 m-2">
<button type="button" class="btn btn-sm btn-pill btn-primary">Berlin</button>
<button type="button" class="btn btn-sm btn-pill">Tokyo</button>
</div>
</div>
</div>
```
### Globe
MapLibre can draw the map as a 3D globe. Set the projection in the `style.load` event, as it is part of the map style. People can turn the globe by dragging it.
```js
map.on('style.load', function () {
map.setProjection({ type: 'globe' })
})
```
Use a low zoom, between 1 and 2, to see the whole globe. To connect two places with an arc, draw a line layer through points on the shortest path between them. The [maps preview page](https://preview.tabler.io/maps.html) has a short function that returns these points.
### Moving the map
Move to another place with `flyTo`, for example from a row of buttons:
+1 -1
View File
@@ -12,7 +12,7 @@ import CaptureScript from '@shared/components/CaptureScript.astro'
window.tabler_map ??= {}
function initMap() {
const themeStyle = () => `https://tiles.openfreemap.org/styles/${document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'liberty'}`
const themeStyle = () => `https://tiles.openfreemap.org/styles/${document.documentElement.getAttribute('data-bs-theme') === 'dark' ? 'dark' : 'positron'}`
const map = new maplibregl.Map({
container: 'map-fullsize',
+102 -23
View File
@@ -61,6 +61,14 @@ const storeCities: [number, number][] = [
]
const stores = storeCities.flatMap(([lng, lat], city) => Array.from({ length: 5 + (city % 4) }, (_, index) => [lng + Math.sin(city * 7 + index * 3) * 0.6, lat + Math.cos(city * 5 + index * 2) * 0.4]))
const hub = { name: 'London', lng: -0.1276, lat: 51.5072 }
const destinations = [
{ name: 'Tokyo', lng: 139.6917, lat: 35.6895 },
{ name: 'Mumbai', lng: 72.8777, lat: 19.076 },
{ name: 'Singapore', lng: 103.8198, lat: 1.3521 },
{ name: 'Nairobi', lng: 36.8219, lat: -1.2921 },
]
const places = [
{ name: 'Museum Island', text: 'Five museums on the Spree, open daily.', photo: 'a-visit-to-the-bookstore.jpg', lat: 52.5169, lng: 13.4019, open: true },
{ name: 'Bellevue Palace', text: 'Residence by the Tiergarten park.', photo: 'book-on-the-grass.jpg', lat: 52.5176, lng: 13.3531, open: false },
@@ -114,11 +122,11 @@ const places = [
<Card>
<CardBody>
<CardTitle>Marker styles</CardTitle>
<CardSubtitle>Draw markers in HTML with <code>.map-marker</code>: a dot, a circle with an icon or a count, a pin, or an avatar.</CardSubtitle>
<CardSubtitle>Draw markers in HTML with <code>.map-marker</code>: a dot, a circle with an icon or a count, a pin, or an avatar. Add <code>.map-marker-label</code> for a short text.</CardSubtitle>
<Map mapId="marker-styles" title="Marker styles" center={[52.522, 13.405]} zoom={11} ratio="4x3">
<div data-lng="13.345" data-lat="52.54"><span class="map-marker map-marker-dot"></span></div>
<div data-lng="13.39" data-lat="52.545"><span class="map-marker map-marker-dot map-marker-red"></span></div>
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /></span></div>
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /><span class="map-marker-label">Store</span></span></div>
<div data-lng="13.35" data-lat="52.512"><span class="map-marker map-marker-green">12</span></div>
<div data-lng="13.405" data-lat="52.52" data-anchor="bottom"><span class="map-marker map-marker-pin"><Icon name="home" /></span></div>
<div data-lng="13.47" data-lat="52.518" data-anchor="bottom"><span class="map-marker map-marker-pin map-marker-red"><Icon name="star" /></span></div>
@@ -180,7 +188,7 @@ const places = [
type: 'line',
source: 'route',
layout: { 'line-cap': 'round', 'line-join': 'round' },
paint: { 'line-color': tablerMapColor('--tblr-primary'), 'line-width': 4 },
paint: { 'line-color': tablerMapColor('--tblr-primary'), 'line-width': 3, 'line-opacity': 0.8 },
})
})
})
@@ -220,7 +228,7 @@ const places = [
<CardBody>
<CardTitle>Clusters</CardTitle>
<CardSubtitle>Group many points into clusters that split as you zoom in. Click a cluster to zoom to it.</CardSubtitle>
<Map mapId="clusters" title="Clusters" center={[48.5, 9]} zoom={3} style="positron" controls />
<Map mapId="clusters" title="Clusters" center={[48.5, 9]} zoom={3} controls />
<CaptureScript>
<script is:inline define:vars={{ stores }}>
document.addEventListener('DOMContentLoaded', () => {
@@ -279,17 +287,18 @@ const places = [
<Card>
<CardBody>
<CardTitle>City switcher</CardTitle>
<CardSubtitle>Move the map to a place with <code>flyTo</code> when a button is clicked.</CardSubtitle>
<div class="btn-group w-100 mb-3" role="group" aria-label="City" id="map-cities-switcher">
{
cities.slice(0, 4).map((city, index) => (
<button type="button" class:list={['btn', index === 0 && 'active']} data-lng={city.lng} data-lat={city.lat}>
{city.name}
</button>
))
}
</div>
<Map mapId="cities" title="City switcher" center={[52.52, 13.405]} zoom={10} ratio="21x9" />
<CardSubtitle>Put buttons over the map and move it to a place with <code>flyTo</code>.</CardSubtitle>
<Map mapId="cities" title="City switcher" center={[52.52, 13.405]} zoom={10}>
<div slot="overlay" class="btn-list position-absolute top-0 start-0 w-auto h-auto m-2" role="group" aria-label="City" id="map-cities-switcher">
{
cities.slice(0, 4).map((city, index) => (
<button type="button" class:list={['btn btn-sm btn-pill', index === 0 && 'btn-primary']} aria-pressed={index === 0 ? 'true' : 'false'} data-lng={city.lng} data-lat={city.lat}>
{city.name}
</button>
))
}
</div>
</Map>
<CaptureScript>
<script is:inline>
document.addEventListener('DOMContentLoaded', () => {
@@ -298,7 +307,10 @@ const places = [
buttons.forEach((button) => {
button.addEventListener('click', () => {
buttons.forEach((item) => item.classList.toggle('active', item === button))
buttons.forEach((item) => {
item.classList.toggle('btn-primary', item === button)
item.setAttribute('aria-pressed', item === button)
})
map.flyTo({ center: [Number(button.dataset.lng), Number(button.dataset.lat)], zoom: 10 })
})
})
@@ -314,12 +326,12 @@ const places = [
<CardBody>
<CardTitle>Bubble map</CardTitle>
<CardSubtitle>Size circles by a value, for example users per city. Hover a circle to see the number.</CardSubtitle>
<Map mapId="bubbles" title="Bubble map" center={[20, 20]} zoom={1} style="positron" ratio="21x9" />
<Map mapId="bubbles" title="Bubble map" center={[20, 20]} zoom={1} ratio="21x9" />
<CaptureScript>
<script is:inline define:vars={{ cities }}>
document.addEventListener('DOMContentLoaded', () => {
const map = window.tabler_map['map-bubbles']
const popup = new maplibregl.Popup({ closeButton: false, offset: 12 })
const tooltip = new maplibregl.Popup({ closeButton: false, offset: 12, className: 'map-tooltip' })
map.on('style.load', () => {
const color = tablerMapColor('--tblr-primary')
@@ -332,7 +344,7 @@ const places = [
id: 'cities',
type: 'circle',
source: 'cities',
paint: { 'circle-color': color, 'circle-opacity': 0.4, 'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 28], 'circle-stroke-width': 1, 'circle-stroke-color': color },
paint: { 'circle-color': color, 'circle-opacity': 0.6, 'circle-radius': ['interpolate', ['linear'], ['get', 'users'], 0, 4, 1600, 24] },
})
})
@@ -340,11 +352,11 @@ const places = [
const city = event.features[0].properties
map.getCanvas().style.cursor = 'pointer'
popup.setLngLat([city.lng, city.lat]).setText(`${city.name}: ${city.users} users`).addTo(map)
tooltip.setLngLat([city.lng, city.lat]).setText(`${city.name}: ${city.users} users`).addTo(map)
})
map.on('mouseleave', 'cities', () => {
map.getCanvas().style.cursor = ''
popup.remove()
tooltip.remove()
})
})
</script>
@@ -353,12 +365,79 @@ const places = [
</Card>
</div>
<div class="col-lg-12">
<div class="col-lg-6">
<Card>
<CardBody>
<CardTitle>Globe</CardTitle>
<CardSubtitle>Show the map as a 3D globe with <code>setProjection</code>, and connect places with arcs.</CardSubtitle>
<Map mapId="globe" title="Globe" center={[32, 62]} zoom={1.3} ratio="4x3" globe>
<div data-lng={hub.lng} data-lat={hub.lat}>
<span class="map-marker map-marker-dot">
<span class="map-marker-label">{hub.name}</span>
</span>
</div>
{
destinations.map((place) => (
<div data-lng={place.lng} data-lat={place.lat}>
<span class="map-marker map-marker-dot" style="--tblr-map-marker-size: 0.625rem">
<span class="map-marker-label">{place.name}</span>
</span>
</div>
))
}
</Map>
<CaptureScript>
<script is:inline define:vars={{ hub, destinations }}>
document.addEventListener('DOMContentLoaded', () => {
const map = window.tabler_map['map-globe']
// Points along the shortest path between two places on the globe
const arc = (from, to, steps = 64) => {
const [lng1, lat1, lng2, lat2] = [from.lng, from.lat, to.lng, to.lat].map((degrees) => (degrees * Math.PI) / 180)
const angle = 2 * Math.asin(Math.sqrt(Math.sin((lat2 - lat1) / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin((lng2 - lng1) / 2) ** 2))
return Array.from({ length: steps + 1 }, (_, step) => {
const start = Math.sin((1 - step / steps) * angle) / Math.sin(angle)
const end = Math.sin((step / steps) * angle) / Math.sin(angle)
const x = start * Math.cos(lat1) * Math.cos(lng1) + end * Math.cos(lat2) * Math.cos(lng2)
const y = start * Math.cos(lat1) * Math.sin(lng1) + end * Math.cos(lat2) * Math.sin(lng2)
const z = start * Math.sin(lat1) + end * Math.sin(lat2)
return [(Math.atan2(y, x) * 180) / Math.PI, (Math.atan2(z, Math.hypot(x, y)) * 180) / Math.PI]
})
}
map.on('style.load', () => {
// Hide the place names of the map style, so only the marker labels are left
map
.getStyle()
.layers.filter((layer) => layer.type === 'symbol')
.forEach((layer) => map.setLayoutProperty(layer.id, 'visibility', 'none'))
map.addSource('arcs', {
type: 'geojson',
data: { type: 'FeatureCollection', features: destinations.map((place) => ({ type: 'Feature', geometry: { type: 'LineString', coordinates: arc(hub, place) } })) },
})
map.addLayer({
id: 'arcs',
type: 'line',
source: 'arcs',
paint: { 'line-color': tablerMapColor('--tblr-primary'), 'line-width': 1.5, 'line-dasharray': [2, 2] },
})
})
})
</script>
</CaptureScript>
</CardBody>
</Card>
</div>
<div class="col-lg-6">
<Card>
<CardBody>
<CardTitle>Draggable marker</CardTitle>
<CardSubtitle>Add <code>draggable: true</code> to let people move a marker, for example to pick an address.</CardSubtitle>
<Map mapId="draggable" title="Draggable marker" center={[52.52, 13.405]} zoom={12} ratio="21x9">
<Map mapId="draggable" title="Draggable marker" center={[52.52, 13.405]} zoom={12} ratio="4x3">
<div data-lng="13.405" data-lat="52.52" data-anchor="bottom" data-draggable><span class="map-marker map-marker-pin"><Icon name="map-pin" /></span></div>
</Map>
</CardBody>
+18 -10
View File
@@ -17,15 +17,17 @@ interface Props {
title?: string;
style?: string;
controls?: boolean;
/** draws the map as a 3D globe instead of a flat map */
globe?: boolean;
}
const { mapId, ratio, center: centerProp, zoom: zoomProp, title, style: styleProp, controls: controlsProp } = Astro.props;
const { mapId, ratio, center: centerProp, zoom: zoomProp, title, style: styleProp, controls: controlsProp, globe = false } = Astro.props;
type MapData = {
title?: string | undefined;
ratio?: string;
card?: boolean;
/** OpenFreeMap style name: liberty (default), bright, positron, dark or fiord */
/** OpenFreeMap style name: positron (default), liberty, bright, dark or fiord */
style?: string | undefined;
/** style used in dark mode, `dark` by default */
darkStyle?: string;
@@ -41,7 +43,7 @@ const hasHtmlMarkers = Astro.slots.has('default');
const mapContainerId = `map-${mapId}`;
const styleUrl = (name: string) => `https://tiles.openfreemap.org/styles/${name}`;
const style = styleUrl(data?.style ?? 'liberty');
const style = styleUrl(data?.style ?? 'positron');
const darkStyle = styleUrl(data?.darkStyle ?? 'dark');
const controls = data?.controls ?? false;
const zoom = data?.zoom ?? 13;
@@ -55,7 +57,8 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
data && (
<div class={`ratio ratio-${ratioClass}`}>
<div>
<div id={mapContainerId} class={`w-100 h-100${data.card ? ' rounded' : ''}`} role="img" aria-label={data.title ?? 'Map'} />
<div id={mapContainerId} class="w-100 h-100 rounded" role="img" aria-label={data.title ?? 'Map'} />
<slot name="overlay" />
</div>
</div>
)
@@ -71,7 +74,7 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
data && (
<CaptureScript>
<!-- BEGIN MAP -->
<script is:inline define:vars={{ mapContainerId, style, darkStyle, controls, zoom, center, markers }}>
<script is:inline define:vars={{ mapContainerId, style, darkStyle, controls, globe, zoom, center, markers }}>
window.tabler_map ??= {};
function initMap() {
@@ -88,11 +91,16 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
map.addControl(new maplibregl.NavigationControl());
}
if (globe) {
// The projection is part of the style, so set it again each time the style loads
map.on('style.load', () => map.setProjection({ type: 'globe' }));
}
markers.forEach((marker) => {
new maplibregl.Marker({ color: 'var(--tblr-primary)' })
.setLngLat(marker.coords)
.setPopup(new maplibregl.Popup({ offset: 32 }).setText(marker.name))
.addTo(map);
const element = document.createElement('span');
element.className = 'map-marker map-marker-dot';
new maplibregl.Marker({ element }).setLngLat(marker.coords).setPopup(new maplibregl.Popup({ offset: 16, closeButton: false }).setText(marker.name)).addTo(map);
});
// Markers written in HTML: the first child is the marker, a <template> is its popup
@@ -102,7 +110,7 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
const popup = item.querySelector('template');
if (popup) {
marker.setPopup(new maplibregl.Popup({ offset: anchor === 'bottom' ? 40 : 20, className: popup.className, maxWidth: 'none', focusAfterOpen: !('open' in item.dataset) }).setDOMContent(popup.content));
marker.setPopup(new maplibregl.Popup({ offset: anchor === 'bottom' ? 40 : 16, className: popup.className, maxWidth: 'none', focusAfterOpen: !('open' in item.dataset) }).setDOMContent(popup.content));
}
marker.setLngLat([Number(item.dataset.lng), Number(item.dataset.lat)]).addTo(map);
+2 -2
View File
@@ -4,13 +4,13 @@
"controls": true
},
"light": {
"title": "Light map style",
"title": "Street map style",
"center": [
51.5285582,
-0.2416782
],
"zoom": 11,
"style": "positron"
"style": "liberty"
},
"markers": {
"title": "Map with markers",