Image Hotspots
<x-wirekit:: lays markers over an image: a shop shows the products in a room, a guide names the parts of a machine. Each marker is a button named after its target. On hover and focus its name shows beside it, and a press sends wirekit:image-hotspots-select, so the page decides what opens, a quick view, a drawer or another page. On the phone a strip under the image lists the targets as links.
Basic Usage
Give the image, its size in pixels and the markers, each with its position in percent of the image and the name of its target:
The markers come in the order Tab reaches them, so put them in the order a reader should meet them. A marker right of the middle shows its name on its left, the others on their right. A name longer than the room between its marker and the edge of the image wraps onto a second line, so it never runs past the image, not even on a phone.
Markers
Each marker is an array:
| Key | Type | Required | Description |
|---|---|---|---|
x, y |
int|float |
yes | Where the center of the marker stands, in percent of the image's width and height, from its top left corner |
label |
string |
yes | The name of the target: the marker's accessible name, the text beside it and the text of its card |
target |
string|int |
no | What the page needs to open the target, an id or a key. It comes back in the event |
href |
string |
no | The link of the target's card in the strip. Without it the card is text |
image |
string |
no | A thumbnail on the target's card |
phoneX, phoneY |
int|float |
no | The position on the phone's file. They fall back to x and y |
A marker without a label would be a button nobody can identify, and one without a position has no place, so both are reported and left out. A position outside 0 to 100 is reported and held at the edge.
A File for the Phone
A phone often shows another crop of the same scene. phone-src gives it, with its own size, and every marker takes its position on it from phoneX and phoneY. The browser switches the file below 48rem, and the markers move at the same width:
<x-wirekit::image-hotspots
src="/img/living-room.jpg"
alt="A living room with a sofa, a floor lamp and a rug"
width="1600"
height="1000"
phone-src="/img/living-room-phone.jpg"
phone-width="900"
phone-height="1200"
:markers="[
['x' => 24, 'y' => 58, 'phoneX' => 30, 'phoneY' => 70, 'label' => 'Sofa Bergen', 'target' => 'sofa-bergen'],
['x' => 71, 'y' => 34, 'phoneX' => 64, 'phoneY' => 22, 'label' => 'Floor lamp Arc', 'target' => 'lamp-arc'],
]"
/>
The image takes no ratio of its own: its box is the image, so a percentage of the box is a percentage of the image, on either file.
The Strip on the Phone
Below 48rem a strip under the image lists the targets, each a link where it has an href, with its thumbnail where it has an image. A press on a marker marks its card and scrolls the strip until the card is in view. The strip is always in the markup, also where the stylesheet hides it, so screen readers and search engines read the targets as links. :strip="false" leaves it out:
Opening the Target
A press sends wirekit:image-hotspots-select from the marker, and it bubbles. Its detail carries index, target and label. In a Livewire component the page opens what it needs:
<x-wirekit::image-hotspots
src="/img/living-room.jpg"
alt="A living room with a sofa, a floor lamp and a rug"
width="1600"
height="1000"
:markers="$hotspots"
x-on:wirekit:image-hotspots-select="$wire.openQuickView($event.detail.target)"
/>
public function openQuickView(string $product): void
{
$this->quickView = Product::where('slug', $product)->firstOrFail();
}
The marker takes the focus when it is pressed, also in Safari, which does not focus a button it clicks. A quick view in a modal or a drawer hands the focus back to it when it closes.
In a Carousel
The component holds no state outside itself, so it works as a slide of a carousel, one image of a campaign after another.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
src |
string |
— | The image's address. Without one nothing renders, and it is reported |
alt |
string |
'' |
The image's text alternative |
srcset |
string|null |
null |
Responsive sources of the image |
sizes |
string|null |
null |
The width the image takes in the layout, for srcset |
width |
int|null |
null |
The image's width in pixels, which with height reserves its box before the file arrives |
height |
int|null |
null |
The image's height in pixels |
phone-src |
string|null |
null |
The file a phone shows below 48rem. See A File for the Phone |
phone-srcset |
string|null |
null |
Responsive sources of the phone's file |
phone-width |
int|null |
null |
The phone file's width in pixels |
phone-height |
int|null |
null |
The phone file's height in pixels |
loading |
string |
'lazy' |
'lazy' or 'eager'. An image at the top of the page loads eager |
fetchpriority |
string|null |
null |
'high', 'low' or 'auto' on the <img>; null leaves it to the browser. 'high' suits an image at the top of the page, which the browser then fetches first |
markers |
array |
[] |
The markers. See Markers |
icon |
string |
'plus' |
The icon each marker shows |
label |
string|null |
"Hotspots" | The name of the markers as a group |
strip |
bool |
true |
The strip of targets under the image on the phone |
strip-label |
string|null |
"In this image" | The name of the strip |
scope |
string|null |
null |
Scoped personalization key |
Events
| Event | Detail | When |
|---|---|---|
wirekit:image-hotspots-select |
{ index, target, label } |
A marker was pressed, with the mouse, a finger or the keyboard |
Accessibility
- Every marker is a
<button>whose name is its target's name, and the markers are a list named bylabel, so a screen reader says how many there are. - The name beside a marker shows on hover and on focus, never on hover alone. Escape hides every name on screen, the one under the pointer as well while the focus is elsewhere, until its marker loses the focus or the pointer (WCAG 1.4.13). In a modal or a drawer, that press leaves the dialog open, and the next Escape closes it.
- A marker measures
--size-wk-sm, 2rem, past the 24 pixels of WCAG 2.5.8. It stands on a fill of its own inside a ring in the text color, so one of the two keeps 3:1 against any image (WCAG 1.4.11). - The strip is a named scroll region the keyboard can reach, and its cards are a list of links.
- The label's fade follows
--transition-wk-durationand stops underprefers-reduced-motion: reduce, and the strip scrolls without a glide there.
Keyboard Interaction
| Key | Action |
|---|---|
Tab |
Moves to the next marker, in the order of markers |
Enter / Space |
Presses the marker |
Escape |
Hides the name beside the focused marker and the one under the pointer. With no name on screen, it closes the dialog around the image |