HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramActiveTraceActiveRecallActiveServicesInsightsGet in touchSource on GitHub
Essentials
MapsHelix Maps puts a frame around a map drawn by MapLibre, Leaflet, or a renderer of
your own. The frame adds a column of controls and a place for the map credit. You
add places, routes, areas, live positions, and data as elements inside the
frame, and @fusion.dev/helix-map connects the frame to the renderer and styles
the map with your theme.Add a mapInstall Helix, the map connector, and MapLibre:bun add @fusion.dev/helix @fusion.dev/helix-map maplibre-glAdd the map frame, an element for MapLibre to draw in, and your places:<helix-maplabel="Our stores"><divid="map"></div><helix-map-markerlabel="Flagship"lat="30.2672"lng="-97.7431"></helix-map-marker></helix-map>Create the map with a style from your theme, then connect it to the frame:import'@fusion.dev/helix/map'import'maplibre-gl/dist/maplibre-gl.css'import{colorsReady,connectMap}from'@fusion.dev/helix-map'import{followTheme,mapLibreDriver,mapLibreStyle}from'@fusion.dev/helix-map/maplibre'import*asmaplibreglfrom'maplibre-gl'// MapLibre 6 runs its worker from a file of its own. Point it there once.maplibregl.setWorkerUrl('/maplibre/maplibre-gl-worker.mjs')constframe=document.querySelector('helix-map')conststyle=(colors)=>mapLibreStyle(colors)constmap=newmaplibregl.Map({attributionControl:false,container:'map',style:style(awaitcolorsReady(frame)),})constconnection=connectMap(frame,mapLibreDriver(map))connection.fit({animate:false})followTheme(map,frame,style)What the connector doesconnectMap links the frame to the renderer:
The frame's pan, zoom, rotate, and tilt buttons and keys move the map, using
your theme's motion settings. The map moves without animation when a user has asked for
reduced motion.
The renderer's own keyboard handling and zoom buttons are turned off, so the
frame's controls are the only ones.
The renderer's credit is shown in the frame's footer.
connection.fit() frames every place on the map.
connection.disconnect() puts back everything connectMap changed.
Use mapLibreDriver(map) for MapLibre GL JS 5 or 6 and leafletDriver(map, L)
for Leaflet 1.9. Neither renderer is bundled with Helix, so you can use the
version you already have. Leaflet draws routes, areas, and points only when you
pass it L. On Leaflet, points are not gathered into counts, and markers move
to their new places when an animated zoom ends.MapLibre 6 ships as modules and runs its worker from its own file,
maplibre-gl-worker.mjs, which imports maplibre-gl-shared.mjs beside it.
Serve both from one folder and call setWorkerUrl with the worker's address
before you create a map, as MapLibre's installation guide shows for your
bundler. In Next.js, copy both files into public/ at build time. MapLibre 6
fixes a sanitizing flaw in earlier versions, so use 6.4.1 or later.Style the map with your thememapLibreStyle(colors) builds a MapLibre style from five colors in your theme:
Land uses surface.
Water uses info.
Parks use success.
Main roads use primary, your brand color, or ink with no brand.
Smaller roads, buildings, borders, and labels use foreground.
It also draws landmarks, highway signs, and neighborhood names:
A landmark is an icon on a disc and its name, in a color for its kind:
parks, museums, schools, hotels, hospitals, stations, and stadiums from zoom
14, and food and shops from zoom 16. Each color is a fixed hue at the
lightness of primary, so the kinds stay apart in any theme.
A highway's number is on its sign, drawn to the Federal Highway
Administration's outlines: the red and blue Interstate shield, the white US
route shield, the white circle of a state route, and a plain plate for other
roads.
Neighborhoods are in spaced capitals, quieter than the towns.
Pass landmarks to choose which kinds the map draws. false draws none, for a
map whose own markers are the places a reader is looking for. A list draws only
those kinds:mapLibreStyle(colors,{landmarks:['nature','transit']})The kinds are culture, food, health, learning, lodging, nature,
shopping, sport, and transit, and MAP_LANDMARKS lists them. The option
holds when followTheme restyles the map, because it builds the style from the
same options.The same call builds a light map for a light theme and a dark map for a dark
theme. Map labels meet a contrast target of 4.5 against the land. Pass
contrast to change it, language to choose which name each place is labeled
with, and tiles and glyphs to use your own tile server.The tiles come from OpenFreeMap unless you pass tiles, and so do the glyphs
unless you pass glyphs or font. OpenFreeMap needs no key. Your reader's
browser requests the map's tiles and glyphs from OpenFreeMap's servers, and
mapSnapshot requests its tiles from them on your server.Pass font to draw the labels in your theme's typeface. MapLibre draws the
letters in the page from the font your page has loaded, so there are no glyph
files to fetch. mapFontFrom(frame) reads the typeface from your theme, and
fontReady(font) waits until its weights are loaded. Wait for it before you
create the map, because MapLibre keeps each letter as first drawn:import{fontReady,mapFontFrom}from'@fusion.dev/helix-map'constfont=mapFontFrom(frame)awaitfontReady(font)constmap=newmaplibregl.Map({container:'map',style:mapLibreStyle(colors,{font})})The style draws the world as a globe when the map is zoomed out. The globe turns
into the flat map as the map zooms in. connectMap hides markers on the far side
of the globe, and a keyboard user who moves to one turns the globe to show it.
Pass globe: false for a flat map at every zoom. MapLibre 4 has no globe, so
pass it there too.followTheme(map, frame, style) restyles the map when your theme changes. It
watches for a new color mode, class, or theme value on the frame or its
ancestors, and for a change in the system's light or dark setting. If your page
changes its theme another way, such as by replacing a stylesheet, call
update() on the object followTheme returns.colorsReady(frame) waits until your theme's colors can be read, which can take
a frame or two after the page loads. If the colors never become available, it
reports which one is missing.Put a map behind your contentSet fill to lay a map behind your page's own content, such as a hero whose
words decide its height. The frame covers its container instead of taking a
height of its own, and draws no border or corners, because the container's edge
is the map's. Give the container a position, such as position: relative:<sectionclass="hero"><helix-mapcredit-placement="bottom-end"filllabel="Downtown Austin"><divid="map"></div></helix-map><divclass="hero-words"><h1>Every stop, one map</h1></div></section>.hero,
.hero-words{position:relative;}Content after the map, with a position of its own, is drawn over it. Give that
content a ground of its own, such as a translucent panel, so it reads over any
part of the map.credit-placement draws the map's credit in a corner over the map instead of in
a strip under it. Use bottom-end or bottom-start, and keep it away from the
corner your controls are in. The credit is one line until it is pointed at or
its links are focused, and then it shows in full. That compact form is the one
OpenStreetMap's attribution guidance accepts for a small credit. Most tile
licenses require the credit to stay visible, so Helix never hides it.Draw the map before it loadsmapSnapshot(view) draws the map on your server as an SVG, from the same tiles
and in the same colors as mapLibreStyle. Put it in the frame after the element
MapLibre draws in, and the page shows a map from its first paint. connectMap
fades it out once MapLibre has drawn the map whole.import{mapSnapshot}from'@fusion.dev/helix-map/snapshot'constview={center:{lat:30.27,lng:-97.74},height:700,pitch:45,width:1200,zoom:14,}constsnapshot=awaitmapSnapshot(view)// Inside helix-map, after <div id="map"></div>, write snapshot.markup.Give MapLibre the same center, zoom, pitch, and bearing, and the snapshot
lines up with the map that replaces it. width and height are the box it is
drawn for, and a smaller box shows its middle. A snapshot draws the land, water,
parks, buildings, and roads, and leaves the labels to MapLibre. Its colors are
your theme's tokens, so it is light or dark with the page and follows a theme
change. Its picture is an empty string when any tile it needs cannot be read
in time, so the picture is whole or absent and the map draws in its place. Each
request is given timeout milliseconds, 3000 by default. Cache only a snapshot
whose picture is not empty.A snapshot reads OpenFreeMap's tiles from your server by default. Pass the
tiles you give mapLibreStyle as mapSnapshot(view, { tiles }) to read
your own.The snapshot carries the map credit. Most tile licenses require the credit
wherever the map is shown, including before MapLibre loads and on a page that
never loads it. markup is the picture followed by the credit your tiles
name, in a <span slot="attribution"> marked data-helix-map-credit, so the
frame shows the credit from the first paint. connectMap hides that credit
once it writes MapLibre's, which names the same tiles. When a tile cannot be
read, the credit stays and markup is the credit alone, so write markup
whether or not the picture was drawn. With OpenFreeMap's tiles, the default,
the credit also stays when their TileJSON does not answer in time.A snapshot also holds the two apart: picture, the map alone, and credit,
the credit as runs of words and links, each { text } or { href, text }.
In a framework that renders the markup for you, such as React, put picture
in an element that is a direct child of the frame and has the
data-helix-map-snapshot attribute. Write credit in a
<span slot="attribution" data-helix-map-credit>, also a direct child of the
frame:<helix-maplabel="Downtown Austin"><divid="map"/><divdangerouslySetInnerHTML={{__html:snapshot.picture}}data-helix-map-snapshot=""/>{snapshot.credit.length>0?(<spandata-helix-map-credit=""slot="attribution">{snapshot.credit.map((part,index)=>part.href?(<adata-link="quiet"href={part.href}key={index}rel="noopener noreferrer"target="_blank">{part.text}</a>):(part.text),)}</span>):null}</helix-map>connectMap hides both elements, and leaves removing them to your framework.A credit you slot yourself, with no data-helix-map-credit, wins. Write
picture alone beside it. A snapshot credit already in the frame is hidden
when connectMap connects.A snapshot is a flat map. A map zoomed out past about 6 is a globe in MapLibre,
so draw snapshots of city and street views.Share the scroll with the pageBy default a map takes every gesture over it: the wheel zooms the map, and one
finger pans it. That suits a map that is the page, such as a full-screen view.
A map embedded in a page, such as a hero, takes the wheel from a reader who is
scrolling past it. Set gestures on the frame to share them:<helix-mapgestures="cooperative"label="Downtown Austin"><divid="map"></div></helix-map>
cooperative leaves the wheel and one finger to the page. The map takes Ctrl
or ⌘ with the wheel, and two fingers. When a gesture passes to the page, the
frame says how to reach the map, where the gesture was. Use it for a map
embedded in a page.
activate takes nothing until the map is pressed. The frame then shows that
the map has the gestures, and gives them back when the reader presses Escape,
moves the pointer off the map, or presses elsewhere on the page.
always, the default, takes every gesture.
The keyboard and the frame's buttons work the same in every mode. connectMap
tells the renderer, so MapLibre and Leaflet behave alike, and MapLibre's own
message is replaced by the frame's. For Leaflet, pass L, as
leafletDriver(map, L). Change the words with wheel-hint-label,
two-finger-hint-label, activate-hint-label, and using-label. Style the
hint and the line with ::part(gesture-hint) and ::part(gesture-status).Show the map once it is drawnMapLibre draws a map in stages: the streets, then the imagery tile by tile,
then the ground rising where there is terrain. connectMap tells the frame when
the map is drawn whole, with every tile the view needs and the ground in place.
The frame then sends helix-map-ready and matches :state(ready). Its ready
property is true from then on, for code that runs after the event.To show a map only once it is finished, fade it in on the state:helix-map#map{opacity:0;transition:opacity0.3s;}helix-map:state(ready)#map{opacity:1;}The routes and areas the frame draws are not part of "drawn whole", so a route
that moves along its progress does not hold the map back. A page that draws its
own map without connectMap calls drawn() on the frame when its map is
finished.What you can add to a mapElementWhat it addshelix-map-markerA named place on the maphelix-map-routeA line, such as a delivery routehelix-map-areaA shape, such as a delivery zonehelix-map-positionA moving point or arrow, such as a vehiclehelix-map-pointsThousands of places loaded from GeoJSONhelix-map-buildingsThe map's buildings, raised to their heights in 3Dhelix-map-rasterMap tiles over the map, such as weather radar or satellite imageryhelix-map-terrainHills and mountains, shaded and raised in 3Dhelix-map-contoursContour lines, each at one heighthelix-map-landcoverWhat covers the ground: forest, grass, farmland, sand, rock, and iceMarkers, routes, areas, positions, points, and buildings take tone, such as
primary or danger, for their color. Raster tiles keep their own colors, and
terrain, contours, and landcover take theirs from your theme.Markers<helix-map-markerlabel="Texas Capitol"lat="30.2747"lng="-97.7405"shape="ring"><spanslot="icon">B</span></helix-map-marker>A marker is a button placed at its latitude and longitude. Its point is as
large as the map's landmarks, in your primary color, with a dot in the middle
and a halo of the same color around it, so your places stand out from the
landmarks. Its label appears when you hover over or focus the marker. The
icon slot holds a letter, a number, or an icon in place of the dot, and
shape sets the marker's form: ring, rounded, square, diamond, or
pin. Pressing a marker sends helix-activate from the frame with the
marker's name, or its label if it has no name.To show that a place has been visited, add passed. The marker is drawn in a
muted color, and screen readers hear its name followed by "passed". Use
passed-label to change that sentence, where {label} is the place's name,
such as {label} (visited).ClustersAdd cluster to the frame to combine markers that would overlap into one
count. The count splits apart as you zoom in, and pressing it zooms in to its
places. Places at the same location, such as two businesses at one address,
spread out around that point when you press their count. Press Escape to
combine them again.To handle a pressed cluster yourself, for example by listing its places in a
panel, pass clusterZoom: false to connectMap and listen for
helix-map-cluster.Routes and areas<helix-map-routedescription="4.2 km"label="Depot to Mission"points="37.7955,-122.3937 37.7644,-122.427"progress="0.4"></helix-map-route>Write points as lat,lng pairs separated by spaces. A route draws straight
lines between its points, so to follow streets, pass the path from a routing
service such as OSRM or Valhalla.
progress, from 0 to 1, draws the part already traveled in a lighter color.
stroke="dotted" draws a route as dots, for a walk off the road network.
distancesAlong(points) measures a route the same way the connector does, so
something you move along the route and the lighter part stay in step.
An area is a shape, such as a delivery zone or a search sector. Set caption to
write a few words on the map with it:<helix-map-areacaption="A"label="Sector A"points="30.2693,-97.7745 30.2693,-97.7700 30.2655,-97.7700 30.2655,-97.7745"tone="success"></helix-map-area>The caption stands inside the area, at the point furthest from its edges. Set
caption-placement="outside" to stand it above the area's northernmost point
instead, clear of what the area holds. It is drawn in the area's color, brought
toward the theme's text color until it reaches a 4.5:1 contrast ratio against
the map, with a halo of the map's land around it. A caption is drawn, not
focusable, so it adds nothing to the tab order. A screen reader hears the area's
label in the frame's list of layers.On Leaflet, pass L, as leafletDriver(map, L), and the caption is drawn as a
marker that nobody can press or reach with the keyboard.Live positions<helix-map-positionaccuracy="30"description="Two stops away"heading="90"label="Van 12"lat="37.78"lng="-122.41"></helix-map-position>Update lat, lng, and heading as the position moves. heading turns it
into an arrow, and accuracy draws a ring with a radius of that many meters. Screen
readers announce its description when it changes, at most once every
announce-every milliseconds. Its movement is not announced.Thousands of points<helix-map-pointsclusterlabel="Food and drink"src="/places.geojson"></helix-map-points>Use helix-map-points for more places than markers can handle. MapLibre draws
all of them as one layer, loaded from a GeoJSON file in src or from data you
set on data. With cluster, nearby points combine into counts, and pressing a
count zooms in to it. Pressing a point sends helix-activate from the layer
with the point's id, or with the property named by name-key. Leaflet draws
each point on its own, which works for hundreds of points.Buildings in 3D<helix-map-buildings></helix-map-buildings>helix-map-buildings raises the buildings in the map's tiles to the heights the
tiles record, in your theme's raised surface color. Set tone to color them by
meaning. The buildings appear from zoom 13. To see their height, tilt the map by
setting pitch when you create it:constmap=newmaplibregl.Map({container:'map',pitch:55,style,zoom:15.5})To let users tilt the map, add tilt to the frame's controls:<helix-mapcontrols="zoom compass tilt fullscreen"label="Downtown Austin in 3D">The 3D button is a toggle. Pressed, it tilts the map 50 degrees toward the
horizon and raises the buildings. Pressed again, it makes the map flat, and the
buildings are the map's own flat footprints. Raised terrain lies flat with them,
and keeps its shading. The compass's needle points north as the map turns, and
pressing the compass turns the map back to north.Leaflet draws maps flat and does not rotate, so a Leaflet map shows no
buildings, and the compass and the 3D button do nothing.Weather and other tile overlays<helix-map-rasterattribution="Radar from Example Weather"frames="https://tiles.example/radar-50m/{z}/{x}/{y}.png https://tiles.example/radar-now/{z}/{x}/{y}.png"label="Weather radar"></helix-map-raster>helix-map-raster draws map tiles over the land and roads, and under your
routes, areas, and place names. Set src to one tile URL, or frames to
several, and set playing to show the frames in order, interval milliseconds
each. Every frame's tiles load once, so the loop plays without a blank frame.
Put the tile provider's credit in attribution. The frame shows it beside
the map's credit.
The frames do not play while the user has asked for reduced motion.
Content that moves for more than five seconds needs a way to pause it, so
give playing a control on your page.
Satellite imagery and map types<helix-map-rasterattribution="Imagery from USGS The National Map"groundlabel="Satellite imagery"max-zoom="16"opacity="1"src="https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}"></helix-map-raster>Set ground on helix-map-raster to make its tiles the map's imagery. Imagery
is a map type, which the frame's map-type attribute chooses:
Absent, the Standard map: your themed map, without the imagery.
hybrid: the imagery in place of the map's land, under its roads and names.
satellite: the imagery alone, without the roads and names.
With layers in the frame's controls, the layers menu opens with a card for
each map type, showing a picture of the map in it. Choosing a card sends
helix-before-change with the type's name, standard, hybrid, or
satellite, and change once map-type holds it. Cancel the first to keep the
current type, or listen for the second to store the choice. Set max-zoom to the deepest
zoom your provider has tiles for, and the map enlarges those tiles when a user
zooms in further.The imagery above is from the U.S. Geological Survey. It is in the public domain
and covers the United States. Some providers put {y} before {x} in their
tile URLs, as this one does.Terrain<helix-map-terrainattribution="Elevation from 3DEP and SRTM data, courtesy of the U.S. Geological Survey"max-zoom="15"raisedsrc="https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"></helix-map-terrain>helix-map-terrain shades the land by its slopes, so hills and valleys show on a
flat map. Set raised to raise the ground to its real height as well, and tilt
the map to see it. Set exaggeration to raise it more than its real height,
such as 1.5. Routes, areas, and buildings lie on the raised ground.The elevation tiles above are Terrain Tiles on AWS, which are free to use and
cover the world. Their tiles store heights in the terrarium encoding, which is
the default. For tiles in the Mapbox encoding, set encoding="mapbox". Credit
the sources your map shows: in the United States, the U.S. Geological Survey.Leaflet draws maps flat, so a Leaflet map shows no terrain.Contour lines<helix-map-contoursmax-zoom="15"src="https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"unit="feet"></helix-map-contours>helix-map-contours draws lines of equal height from the same elevation tiles
as terrain. The lines are closer together as the map zooms in, from 500 meters
apart over a region to 5 meters at street level. Every fifth line is heavier and
labeled with its height, in meters, or in feet with unit="feet". They show
from zoom 9, and lie on the ground when it is raised.Land cover<helix-map-landcover></helix-map-landcover>helix-map-landcover colors the ground by what covers it: forest and grass in
greens, farmland in wheat, wetland in teal, sand in tan, rock in gray, and ice
in pale blue. Each color is mixed into your theme's land color. It draws over
parks and under water and roads, so a park shows its meadows and forests.Turn layers on and off<helix-mapcontrols="zoom compass layers fullscreen"label="Downtown Austin">Add layers to the frame's controls for a layers button. It opens a menu with
a tile for each layer: a picture of what the layer draws, and its label. A
tile is lit while its layer is shown. Pressing a tile sets its layer's hidden
attribute, and connectMap does not draw a hidden layer. Write hidden on a
layer to start it turned off.Before it sets hidden, a tile sends helix-before-check from the layer, with
checked true when the layer is to be shown. Store the choice from it, or
cancel it to keep the layer as it is.Give layers the same group to show them as one tile, such as a route and the
dotted legs at its ends. The tile takes the name of the first layer in the
group, and shows or hides every layer in it. Keep labels short, such as
"Walking route", and put the rest in description, which screen readers read
after the label.Use your own icons in the controlsEach control's glyph is a slot, so the controls can use the same icon set as
the rest of your page. An icon you slot in is sized like the glyph it replaces:<helix-mapcontrols="layers zoom compass fullscreen"label="Our stores"><divid="map"></div><svgslot="zoom-in-icon"viewBox="0 0 16 16"><pathd="M7 2h2v5h5v2H9v5H7V9H2V7h5Z"/></svg><svgslot="zoom-out-icon"viewBox="0 0 16 16"><pathd="M2 7h12v2H2Z"/></svg></helix-map>The slots are fullscreen-icon, layers-icon, tilt-icon, zoom-in-icon,
zoom-out-icon, rotate-left-icon, rotate-right-icon, pan-up-icon,
pan-down-icon, pan-start-icon, pan-end-icon, and face-north-icon. The
compass sets --map-needle-turn to the angle that points north, so an icon in
face-north-icon turns with the map when its style is
rotate: var(--map-needle-turn).Keyboard and screen readers
The frame is one tab stop. Arrow keys pan the map, and the plus and minus keys
zoom it. Shift with Left or Right rotates the map while the compass, pan, or
rotate is in controls, and Shift with Up or Down tilts it while tilt is.
Keys with Control, Command, or Alt are left to the browser, so page zoom and
Back still work with focus on the map.
The buttons in controls are a toolbar and one tab stop. Arrow keys move
between them by where they sit, Shift with an arrow still turns or tilts the
map, and Enter or Space presses a button.
The markers are one tab stop. Arrow keys move between them in the order you
wrote them. The map pans to show a marker that is off screen. If your page
removes the marker that has focus, focus moves to the next marker, or to the
one before when it was the last.
A points layer is one tab stop over the points and counts in view. Arrow keys
move top to bottom and along each line. Enter on a count zooms in to it.
Screen readers find the map's shown layers in a list inside the frame, named
by their label and description.
With a pointer, dragging is the only way to move and turn the map. Where your
map has to work without dragging, as WCAG 2.2 asks at level AA, add pan and
rotate to controls for buttons around the compass that move and turn it.
In the layers menu, Tab moves between the tiles, Space or Enter turns a layer
on or off, and Escape closes the menu.
After the map moves in answer to a key or button, screen readers hear what
changed once it stops: "Moved left", "Zoom level 14", "Facing 15 degrees",
or "Tilted 10 degrees". Each sentence is a -label property, such as
zoomed-label and moved-left-label, so you can write it in your page's
language.
If your page moves its own map in answer to helix-map-pan and the other
events, set the frame's zoomLevel, bearing, and pitch to where the map
now is, then call its moved(). The frame says nothing until it is told, so
it never describes a move that did not happen.
Write a driverTo use another renderer, write a driver: an object that moves the renderer's
view when the frame asks, and tells the frame when the view moved. connectMap
calls it. mapLibreDriver and leafletDriver are drivers, and the MapDriver
type in @fusion.dev/helix-map describes every method.UnitsQuantityUnitA place{ lat, lng } in degreesA position on screenCSS pixels from the top left of the renderer's box, right and down positiveBearingDegrees clockwise from northTiltDegrees from looking straight down, 0 being flatZoomThe renderer's own levelsMotion{ duration, easing }, where duration is in milliseconds and easing(t) maps 0 to 1 onto 0 to 1, or nullmotion comes from your theme's motion tokens. It is null when the user has
asked for reduced motion, and the driver then moves at once.Required methods
panBy(x, y, motion) moves the view by x and y CSS pixels. The frame
asks for a fraction of its own box, so an arrow key moves the map by the same
share of the screen at every zoom. On a right-to-left page, connectMap
turns the frame's start and end into left and right before it calls the
driver.
zoomBy(direction, motion) zooms one step in, for 1, or out, for -1. A
step is whatever the renderer calls one, usually one level.
Optional methodsEach optional method turns on a feature. Without it, the feature is off and
nothing fails.MethodWhen connectMap calls itWhat it must doTurns ononView(moved)Once, at connectCall moved on every frame the view moves, and return a function that stopsMarkers that follow the map, and the announcements after a moveproject(place)On every view change, for each markerReturn where place falls, in CSS pixelsPlacing markers and positions. Without it, markers stay hiddenzoom()After each view changeReturn the zoomSaying "Zoom level 14" after a zoombearing(), pitch()After each view changeReturn the bearing and the tiltThe compass needle, the 3D button's pressed state, and arrows that stay correct on a turned maprotate({ by, to }, motion)On the rotate buttons, Face north, and Shift with Left or RightTurn by by degrees, or to the bearing toRotatingtilt({ by, to }, motion)On the 3D button, and Shift with Up or DownTilt by by degrees, or to toTiltingshow(place, motion)When the keyboard reaches a marker that is off screenBring place to the middle of the view, at place.zoom where it has oneWalking markers off screenfit(bounds, padding, motion)On connection.fit(), and on a press on a clusterFrame the northEast and southWest corners, inset by padding pixelsconnection.fit() and zooming in to clusterssilenceKeyboard()Once, at connectTurn off the renderer's own keys, and return a function that turns them back onOne set of keysfocusTarget()Once, at connectReturn the element the renderer made focusableOne tab stop. connectMap sets its tabindex to -1 and puts it back on disconnectreplaceZoom()Once the frame has drawn its zoom buttonsRemove the renderer's zoom buttons, and return a function that puts them backOne set of zoom buttonscredits(changed)Once, at connectReturn read(), the credits as markup, and stop(); call changed when they changeThe map credit in the frame's footerready(done)Once, at connectCall done when the view is drawn whole, and return a function that stops waitinghelix-map-ready and :state(ready)gestures(mode)When the frame's gestures changesTake the wheel and touches as free, cooperative, or off saysSharing the scroll with the pagelayers(read, events)Once, at connectDraw read(), and draw it again on update()Routes, areas, points, and tile layerslabels(shown)When the map type changesShow or hide the renderer's own roads and namesThe Satellite map typepreviews(ask)When the layers menu opens after the view movedResolve to an image URL for each map typePictures in the layers menubehind(place)With projectReturn true for a place on the far side of a globeHiding markers behind a globeconnection.disconnect() calls every function a method returned, gives the
renderer its gestures back with gestures('free'), takes the markers off their
places, puts the focus target's tabindex back as it was, and stops listening
to the frame. Call it before you remove the renderer.How the frame says a moveThe frame's keys and buttons only ask. The frame says "Moved left" or "Zoom
level 14" after the driver calls the moved it gave to onView, and once the
view has been still for a moment. A driver without onView never makes the
frame speak, so the frame never describes a move that did not happen.ExampleThis driver shows a plan drawn as one image, such as a floor plan, whose edges
are at known latitudes and longitudes. It moves the image with a CSS transform,
places markers, and animates over the page's motion. The same driver runs in
our specs, so this example stays correct.import{connectMap}from'@fusion.dev/helix-map'constplanDriver=(image,plan)=>{constview={scale:1,x:0,y:0}constlisteners=newSet()letmoving=0constdraw=()=>{image.style.transformOrigin='0 0'image.style.transform=`translate(${view.x}px, ${view.y}px) scale(${view.scale})`for(constmovedoflisteners){moved()}}// Moves the view to `next` over the page's motion, or at once where it is null.constgo=(next,motion)=>{construn=++movingif(!motion||motion.duration<=0){Object.assign(view,next)draw()return}constfrom={...view}conststart=performance.now()conststep=(now)=>{if(run!==moving){return}constt=Math.min(1,(now-start)/motion.duration)constk=motion.easing(t)view.x=from.x+(next.x-from.x)*kview.y=from.y+(next.y-from.y)*kview.scale=from.scale+(next.scale-from.scale)*kdraw()if(t<1){requestAnimationFrame(step)}}requestAnimationFrame(step)}return{onView:(moved)=>{listeners.add(moved)return()=>listeners.delete(moved)},panBy:(x,y,motion)=>go({...view,x:view.x-x,y:view.y-y},motion),project:({lat,lng})=>({x:view.x+((lng-plan.west)/(plan.east-plan.west))*plan.width*view.scale,y:view.y+((plan.north-lat)/(plan.north-plan.south))*plan.height*view.scale,}),zoom:()=>Math.log2(view.scale),zoomBy:(direction,motion)=>{constbox=image.parentElement.getBoundingClientRect()constk=direction>0?2:0.5const[cx,cy]=[box.width/2,box.height/2]go({scale:view.scale*k,x:cx-(cx-view.x)*k,y:cy-(cy-view.y)*k},motion)},}}constframe=document.querySelector('helix-map')constconnection=connectMap(frame,planDriver(frame.querySelector('img'),{east:0.004,height:600,north:51.502,south:51.5,west:0,width:800,}),)width and height are the image's size in CSS pixels at zoom 0. To animate
a move your page makes itself, read the same motion with
motionOf(element, pace).Test a driverOur specs test each driver twice. A stand-in renderer records what the driver
asks it, which runs anywhere. Then the browser specs connect a frame to the real
MapLibre and Leaflet with a style that loads nothing, press the frame's keys and
buttons, and check that the view moved and that the frame said so. Test your
driver the same way: connect it to a frame, press a key on the frame's region
part, and wait for the live part to say the move.