Blog · Technical
Why image hotspots drift on mobile — and how to fix it
Your hotspots line up perfectly on desktop and slide halfway off the picture on a phone. It is not a rendering quirk; it is a coordinate-system mismatch, and there are exactly two causes.
Free forever plan · No credit card · Works in Wix, Webflow, WordPress, Shopify, Squarespace

Cause 1: pixel coords
<area> coordinates are absolute. CSS scales the image; it does not scale them.
Cause 2: letterboxing
With object-fit, percentages of the container stop being percentages of the picture.
The fix
Percentages measured against the rendered image box — no resize listener required.
What you get
- Absolute <area> coords never rescale with a responsive image
- Store hotspots as percentages of the image, once, at authoring time
- Add translate(-50%, -50%) so the marker centre sits on the point
- Measure the contained image box when object-fit letterboxes the picture
- Use ResizeObserver, not window resize, to catch container-only changes
- Give every marker ~44px of tap area on touch devices
The short version
A hotspot has to be described in the same coordinate system as the pixels a visitor is actually looking at. Legacy image maps describe it in the image's natural pixels, which stop matching the rendered pixels the moment the image is responsive. Everything below is a consequence of that one mismatch.
Cause 1: absolute coordinates against a scaled image
Take a 1200 x 600 diagram with a hotspot on a valve at its centre. In an HTML image map that is a literal pair of numbers:
<img src="diagram.png" usemap="#m" style="width:100%">
<map name="m">
<area shape="circle" coords="600,300,20" href="/valve">
</map>On a 390px-wide phone the browser paints that image at about 32.5% scale — roughly 390 x 195 CSS pixels. But coords still says 600,300. The clickable circle is now 210px to the right of the image's right edge and 105px below its bottom edge. The hotspot has not moved so much as vanished.
The trap is that it looks fine while you are building it, because you build at full width. Halve the browser window and the whole map degrades in one motion.
The maths that fixes it
Convert once, at authoring time, from pixels to a fraction of the image:
xPercent = (600 / 1200) * 100 // 50%
yPercent = (300 / 600) * 100 // 50%Now the marker is positioned as left: 50%; top: 50% inside a wrapper sized to the image. Half of 1200px is 600px; half of 390px is 195px. The marker lands on the valve at both widths, at every width in between, inside a modal, inside a 320px sidebar, and after a pinch-zoom — with no media queries and no resize listener.
Add transform: translate(-50%, -50%) so the marker's centre sits on the point rather than its top-left corner. Skipping that gives a constant half-marker offset that people often misdiagnose as a coordinate bug.
Cause 2: object-fit letterboxing (the subtle one)
This is the bug that survives the switch to percentages, and it is the one that wastes an afternoon. If your image is styled with object-fit: contain in a box with a different aspect ratio, the rendered picture is smaller than the box it lives in. There are transparent bars — letterboxing — on two sides.
Position a marker at 50%/50% of the box and it lands at the centre of the box, not the centre of the picture. Every marker is off by the size of the bar, and the error changes as the container's aspect ratio changes, so it looks correct at one breakpoint and wrong at another.
With object-fit: cover it is worse in a different way: the picture is cropped, so part of your coordinate space is not on screen at all.
The fix is to measure the contained image box and position against it:
const scale = Math.min(boxW / naturalW, boxH / naturalH)
const drawnW = naturalW * scale
const drawnH = naturalH * scale
const offsetX = (boxW - drawnW) / 2
const offsetY = (boxH - drawnH) / 2
const markerX = offsetX + (xPercent / 100) * drawnW
const markerY = offsetY + (yPercent / 100) * drawnHRecompute that on resize with a ResizeObserver rather than a window resize event, because the container can change size without the window doing so — a sidebar opening, an accordion expanding, a font loading. This is exactly what Interactive Image does internally, which is why hotspots hold their position inside an arbitrary iframe on someone else's site.
Two smaller causes worth ruling out
- Measuring before the image loads. If you read the image's dimensions before it has decoded, you get zero, and every marker stacks in the corner. Wait for the load event, or drive the layout from a known aspect ratio so the box is correct before the pixels arrive.
- Re-cropping the image after placing hotspots. Percentages are relative to the frame. Replace the image with a differently-cropped export and every percentage now refers to a different point. Re-export at the same crop, or expect to nudge the markers.
Why not just use a resizer library?
Scripts like image-map-resizer keep <area> coordinates in sync by rewriting them on every resize. It works, and if you have a large legacy map it is a reasonable stopgap. The costs are real though:
- An extra script and a resize listener on every page hosting the map.
- Nothing visible on the image, so visitors cannot tell what is clickable — the same usability problem legacy maps always had.
- Links only. No popup, no photo, no description, no CTA.
- Known trouble with lazy-loaded images and CSS transforms, where the natural size the library reads and the size the browser paints disagree.
Percentage-positioned overlay markers avoid the whole category. There is nothing to recompute because nothing was ever stored in pixels.
A quick diagnostic checklist
- Resize the browser slowly. Drift that grows with width means pixel coordinates (cause 1).
- Change the container's aspect ratio. Drift that appears only in some shapes means object-fit letterboxing (cause 2).
- A constant half-marker offset in one direction means a missing
translate(-50%, -50%). - All markers in the top-left corner means dimensions were read as zero before load.
- Correct on desktop, wrong only inside an embed, means the embed's container is sized differently from the editor's — check the aspect ratio in the iframe snippet.
Don't forget the tap target
Alignment is only half of mobile. A visually correct 10px dot is still a miserable tap target: aim for roughly 44px of hit area with invisible padding around the marker, and open popups as a full-width card or bottom sheet rather than a tooltip pinned to the dot. More on that in the accessibility guide, and on the mechanics in the responsive image map generator page.
Frequently asked questions
Is percentage positioning less precise than pixels?
No. It is the same point expressed as a ratio, and it is stored as a float — so precision is limited by how carefully you place the marker, not by the format.
Does this apply to SVG overlays too?
SVG gets it right for free when you use a viewBox, because the coordinate system scales with the element. The failure mode returns as soon as you mix HTML markers over an SVG without accounting for its rendered box.
Do I have to convert my existing image map by hand?
You can, by dividing each coordinate by the image's natural width and height — but re-clicking the points on the image is usually faster than the arithmetic. The image map generator page walks through both.
Turn any image into a guided experience.
Free forever plan. Paste one iframe into your site and ship.
Create your interactive image
