Custom Markers and Popups โ Branded Map Icons
Why Custom Markers Matter
Default Leaflet markers are functional but generic. Custom markers make your map instantly recognizable, convey meaning (campsite vs. trailhead vs. viewpoint), and provide a better user experience. Rich popups turn markers into information cards.
Why this matters for your career:
- Custom markers differentiate your map from generic examples
- Visual hierarchy helps users understand the map at a glance
- Rich popups reduce the need for a separate info panel
- Custom icons are a common client requirement for branded applications
Custom Marker Icons
Using L.divIcon (No Image Files Needed)
// Simple colored circle marker
const campsiteIcon = L.divIcon({
className: 'custom-marker',
html: '<div class="marker-icon campsite"></div>',
iconSize: [30, 30],
iconAnchor: [15, 30], // bottom-center
popupAnchor: [0, -30] // above the icon
});
const trailheadIcon = L.divIcon({
className: 'custom-marker',
html: '<div class="marker-icon trailhead">๐ฅพ</div>',
iconSize: [30, 30],
iconAnchor: [15, 30],
popupAnchor: [0, -30]
});
const viewpointIcon = L.divIcon({
className: 'custom-marker',
html: '<div class="marker-icon viewpoint">๐ท</div>',
iconSize: [30, 30],
iconAnchor: [15, 30],
popupAnchor: [0, -30]
});
CSS for Custom Icons
.custom-marker {
background: none !important;
border: none !important;
}
.marker-icon {
width: 30px;
height: 30px;
border-radius: 50% 50% 50% 0;
transform: rotate(-45deg);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.marker-icon.campsite {
background: linear-gradient(135deg, #22c55e, #16a34a);
}
.marker-icon.trailhead {
background: linear-gradient(135deg, #3b82f6, #2563eb);
}
.marker-icon.viewpoint {
background: linear-gradient(135deg, #f59e0b, #d97706);
}
/* Fix the rotation so icon content faces upright */
.marker-icon span,
.marker-icon::before {
transform: rotate(45deg);
}
Using Image Icons
const campsiteIcon = L.icon({
iconUrl: '/icons/campsite.svg',
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32],
shadowUrl: '/icons/marker-shadow.png',
shadowSize: [41, 41],
shadowAnchor: [13, 41]
});
// Create themed markers
const marker = L.marker([25.033, 121.565], { icon: campsiteIcon })
.bindPopup('<b>Sunset Ridge Campground</b>')
.addTo(map);
Marker Categories
| Type | Icon Color | Emoji | Use Case | |------|-----------|-------|----------| | Campsite | Green | โบ | Camping spots | | Trailhead | Blue | ๐ฅพ | Hiking trail starts | | Viewpoint | Orange | ๐ท | Scenic viewpoints | | Parking | Gray | ๐ ฟ๏ธ | Parking areas | | Water Source | Cyan | ๐ง | Water refill stations | | Toilet | Purple | ๐ป | Restroom facilities | | Emergency | Red | ๐ | Emergency contact points |
Rich Popups
Basic Popup
marker.bindPopup(`
<h3>Sunset Ridge Campground</h3>
<p>Beautiful mountain ridge with panoramic views. Suitable for tents and small RVs.</p>
`);
Rich Popup with Card Layout
marker.bindPopup(`
<div class="popup-card">
<img src="/images/sunset-ridge.jpg" alt="Sunset Ridge" class="popup-image" />
<div class="popup-content">
<h3>Sunset Ridge Campground</h3>
<div class="popup-meta">
<span class="badge">โบ Campsite</span>
<span class="rating">โ
โ
โ
โ
โ</span>
</div>
<p>Beautiful mountain ridge campsite with panoramic sunrise views. Suitable for tents and small RVs.</p>
<div class="popup-details">
<div><strong>Elevation:</strong> 1,200m</div>
<div><strong>Price:</strong> NT$800/night</div>
<div><strong>Amenities:</strong> Water, Toilet, Fire Pit</div>
</div>
<div class="popup-actions">
<a href="/campsites/sunset-ridge" class="btn">View Details</a>
<button onclick="getDirections(25.033, 121.565)" class="btn btn-secondary">Directions</button>
</div>
</div>
</div>
`);
Popup CSS
.popup-card {
width: 280px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
.popup-image {
width: 100%;
height: 140px;
object-fit: cover;
border-radius: 8px 8px 0 0;
}
.popup-content {
padding: 12px;
}
.popup-content h3 {
margin: 0 0 4px;
font-size: 16px;
color: #1a1a2e;
}
.popup-meta {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.badge {
background: #dcfce7;
color: #166534;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.rating {
color: #f59e0b;
font-size: 14px;
}
.popup-details {
font-size: 13px;
color: #4b5563;
margin-bottom: 12px;
}
.popup-details div {
margin-bottom: 2px;
}
.popup-actions {
display: flex;
gap: 8px;
}
.popup-actions .btn {
flex: 1;
text-align: center;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
text-decoration: none;
cursor: pointer;
border: none;
}
.btn {
background: #22c55e;
color: white;
}
.btn-secondary {
background: #e5e7eb;
color: #374151;
}
/* Mobile popup */
@media (max-width: 480px) {
.popup-card {
width: 220px;
}
.popup-image {
height: 100px;
}
.popup-content h3 {
font-size: 14px;
}
}
Handling Popup Events
marker.on('click', function(e) {
console.log('Marker clicked:', e.target.getLatLng());
// Track analytics
gtag('event', 'marker_click', {
campsite_name: 'Sunset Ridge',
campsite_id: 'abc-123'
});
});
marker.on('popupopen', function() {
console.log('Popup opened');
// Pause map interaction while popup is open
map.dragging.disable();
});
marker.on('popupclose', function() {
console.log('Popup closed');
map.dragging.enable();
});
Summary
Custom markers and rich popups transform a generic map into a branded, informative, and user-friendly application. Use L.divIcon for pure CSS markers or L.icon for image-based markers. Build rich popups with images, metadata, ratings, and action buttons.
Key takeaways:
L.divIconcreates CSS-only markers (no image files needed)L.iconloads custom image iconsiconAnchorsets the point that aligns with the map coordinatepopupAnchorcontrols where the popup appears relative to the icon- Rich popups include images, badges, ratings, and action buttons
- Style popup content with CSS for a card-like appearance
- Use different marker shapes/colors for different POI types
- Handle popup events for analytics and interaction control
- Make popups responsive for mobile devices
- Emoji markers are a quick way to add visual categorization
What's Next: Marker Clustering
The next chapter covers marker clustering โ handling hundreds of campsite markers with efficient clustering for a clean, performant map.