Leaflet Side-by-Side Layer Comparison — Free HTML CSS JS Snippet

Leaflet Side-by-Side Layer Comparison · Misc · Plain HTML, CSS & JS · Live preview

What's included

Features

Zero-dependency swipe compare
Built with plain CSS clip-path, no comparison plugin needed.
Compositor-level performance
clip-path costs nothing extra to redraw, unlike a JS pixel mask.
Real accessible range input
A genuine slider element, not a custom-built drag handler.
Percentage-based, resize-safe
No coordinate recalculation needed after a window resize.
Works with any two tile sources
Swap either layer's URL for any XYZ tile provider.
Standard Leaflet map underneath
Pan, zoom, and attribution all work normally.

About this UI Snippet

Leaflet Side-by-Side Layer Comparison — a Swipe Divider Built on CSS clip-path

Screenshot of the Leaflet Side-by-Side Layer Comparison snippet rendered live

Comparing two map layers — a street map against a topographic one, old imagery against new, one data source against another — is most intuitive as a draggable divider you swipe across, revealing one layer on each side. The well-known way to build this is the leaflet-side-by-side plugin; this snippet reimplements the same interaction from first principles with a single CSS property, so the technique itself — not just the plugin's API — is what's actually being taught.

Both layers render on the same map, stacked

Both tile layers are added to the same L.map instance with addTo(map), which stacks them in DOM order — the second one added (after) sits visually on top of the first. Without any further styling, only the top layer would ever be visible; the entire trick is controlling how much of that top layer is actually painted.

clip-path does the reveal, not opacity or z-index tricks

The core technique is one line: after.getContainer().style.clipPath = 'inset(0 0 0 ' + pct + '%)'. inset()'s four values are top/right/bottom/left, so clipping pct% from the left means the top layer's pane is only actually painted from that percentage rightward — the bottom layer shows through everywhere to the left of it, with a hard, crisp edge and zero performance cost, since clip-path is a compositor-level CSS property, not a JavaScript-computed pixel mask.

The range input is invisible except for its thumb

The slider itself is a real <input type="range"> for genuine accessibility and touch support, but its track is made invisible (height: 0) and only its thumb is styled into a visible drag handle — the actual visual divider line is a separate, purely decorative <div> positioned at the same percentage, kept in sync by the same updateClip function that sets the clip-path.

One function updates everything, on input and on resize

updateClip positions the divider line, sets the clip-path, and is called both on the slider's input event and on window resize — since it always works in percentages rather than pixels, a resize needs only a redraw at the same percentage, not any recalculated coordinate math.

Reusing it

Swap either tile layer's URL for any other XYZ tile source — a different date's imagery, a different data provider, a labeled vs. unlabeled basemap — and the comparison mechanism itself needs no changes.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to reach for an extra plugin dependency to build this. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how the CSS clip-path inset() function reveals only part of the top map layer, and why that approach is cheaper to keep smooth during dragging than a JavaScript-computed pixel mask or canvas overlay would be. The same assistant can help optimize it — ask whether the resize listener should be debounced for a page with frequent layout shifts, and whether the same clip-path technique could be applied vertically instead of horizontally for a top/bottom comparison layout. It's also useful for extending the effect: ask it to add labels that fade in/out based on which side is currently more revealed, keyboard arrow-key support for nudging the divider, or a way to lock the comparison at specific preset positions. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a draggable side-by-side comparison slider for two map tile layers using Leaflet.js (load Leaflet's CSS and JS from a CDN, no other library), in plain HTML, CSS, and JavaScript, without using any dedicated comparison plugin.

Requirements:
- Add two different tile layers to the same map instance, stacked so one renders visually on top of the other.
- Implement the comparison reveal using a CSS clip-path applied directly to the top layer's DOM container, clipping it based on a percentage value so only the portion to one side of a divider position is actually visible, letting the bottom layer show through on the other side — do not use a canvas-based mask or per-pixel JavaScript computation for the reveal itself.
- Overlay a real, accessible range input (type="range") on top of the map that controls the divider's percentage position, styled so its track is invisible and only its thumb appears as a draggable circular handle.
- Render a separate, purely visual thin divider line positioned at the same percentage as the range input's current value, kept in sync with it on every input event.
- Ensure the divider and the clip-path both use percentage-based positioning (not fixed pixel values) so the comparison continues to work correctly if the browser window or map container is resized, without needing to recalculate any pixel coordinates.
- The map underneath should support normal panning and zooming with both layers moving together in sync.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="lc-wrap">
  <div class="lc-bar">
    <div class="lc-title">Street Map vs. Topographic</div>
    <div class="lc-labels"><span>Street map</span><span>Topographic</span></div>
  </div>
  <div class="lc-map" id="lcMap"></div>
</div>

Step by step

How to Use

  1. 1
    Add the Leaflet CDNLoad leaflet.css and leaflet.js before the snippet's JS runs.
  2. 2
    Paste HTML, CSS, and JSTwo tile layers render with a draggable divider at the center.
  3. 3
    Drag the round handleThe divider moves and reveals more of either layer.
  4. 4
    Drag it fully left or rightSee either layer in full, edge to edge.
  5. 5
    Pan or zoom the mapBoth layers move together, the comparison stays intact.
  6. 6
    Resize the browser windowThe divider stays at the same relative percentage.

Real-world uses

Common Use Cases

Before/after satellite or aerial imagery
Compare historical and current imagery directly.
Urban planning and development visualization
Show proposed versus existing conditions.
Climate and environmental change comparisons
Two dates of the same region side by side.
Different basemap style comparisons
Topographic versus street map, or two vendors' tiles.
Construction or disaster-impact documentation
Pair with the GeoJSON choropleth elsewhere in this collection for a data-layer companion.
Learning CSS clip-path techniques
A clear, non-map-specific reference for compositor-based reveals.

Got questions?

Frequently Asked Questions

Both tile layers are stacked on the same map, with the "after" layer rendered on top. A CSS clip-path with an inset() value is applied to the after layer's DOM container, clipping away everything to the left of the divider's percentage position — so only the region to the right of the handle actually gets painted from that top layer, letting the layer underneath show through everywhere to the left.

clip-path is a compositor-level CSS property, meaning the browser's rendering engine handles the clipping during compositing without re-running any JavaScript or recalculating pixel data on every frame. A JS-based approach (drawing to a canvas, computing a clip region manually) would need to recompute and repaint on every drag movement and every map interaction, which is both more code and meaningfully more expensive to keep smooth.

Using a real <input type="range"> preserves genuine keyboard and touch accessibility that a custom-built drag handler would have to reimplement from scratch. Its default track visual isn't wanted here since a separate, purely decorative divider line already shows the split position, so the track's height is set to zero and only the thumb (the actual draggable circle) is given custom styling.

The divider position and the clip-path value are both expressed as a percentage, not a fixed pixel offset. Because percentages are relative to the container's current size, redrawing at the same percentage value after a resize automatically produces the correct new pixel position — no coordinate recalculation or resize-specific math is needed, only re-running the same update function.

Replace either tile layer's URL template with any other XYZ tile source — a different date's imagery, a different map provider, or a labeled versus unlabeled basemap variant. The comparison mechanism (the clip-path reveal, the synced divider, the range input) works identically regardless of what the two tile layers actually show.