How Do SMIL focusIn and focusOut Triggers Handle Hover?
Synchronized Multimedia Integration Language (SMIL) enables
declarative, event-driven animations inside Scalable Vector Graphics
(SVG) without requiring external JavaScript. While pointer hovering is
traditionally captured through mouseover and
mouseout events, focusin and
focusout events provide focus-driven interactivity that
often works in tandem with hover states to support accessible
navigation. Understanding how SMIL parses event base values, binds
trigger conditions, and differentiates pointer movement from element
focus allows developers to build robust, interactive vector
graphics.
Understanding SMIL Event-Value Timing
SMIL animations rely on timing attributes—principally
begin and end—to define when an animation
starts, repeats, or terminates. Instead of using fixed clock values such
as begin="2s", interactive graphics utilize event-value
syntax.
An event-value declaration follows the pattern
[element-id.]event-symbol[+clock-value]. When the specified
DOM event fires on the target element (or the animation's parent element
if omitted), the SMIL timing engine registers a new syncbase time
instance and executes the corresponding state transition.
<rect id="targetBox" width="100" height="100" fill="blue" tabindex="0" />
<animate
xlink:href="#targetBox"
attributeName="fill"
to="orange"
begin="targetBox.focusin"
end="targetBox.focusout"
dur="0.3s"
fill="freeze" />Focus Events vs. Pointer Hover Events
Although focusin and focusout are sometimes
conflated with pointer hovering, they represent distinct interaction
models in DOM Level 3 Events and SVG specifications:
mouseoverandmouseout: Fired when a pointing device moves over or leaves the visual geometry of an element.focusinandfocusout: Fired when an element receives or loses focus, either via keyboard navigation (such as theTabkey), assistive technology, or programmatic.focus()calls.
For an SVG element to dispatch focusin and
focusout events, it must be focusable. By default, most SVG
shapes are not focusable unless they are wrapped inside an
<a> link element or explicitly given a
tabindex attribute.
Combining Hover and Focus for Hybrid Interactions
In modern interactive design, animations often need to respond identically whether a user hovers with a mouse or navigates with a keyboard. SMIL supports semicolon-delimited lists in timing attributes, enabling a single animation element to listen for both pointer hover and focus triggers simultaneously.
<svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<circle id="actionButton" cx="100" cy="100" r="40" fill="#3498db" tabindex="0">
<animate
attributeName="r"
to="50"
begin="actionButton.mouseover; actionButton.focusin"
end="actionButton.mouseout; actionButton.focusout"
dur="0.2s"
fill="freeze" />
<animate
attributeName="fill"
to="#e74c3c"
begin="actionButton.mouseover; actionButton.focusin"
end="actionButton.mouseout; actionButton.focusout"
dur="0.2s"
fill="freeze" />
</circle>
</svg>When the user hovers over #actionButton,
actionButton.mouseover triggers the expansion. When the
user tabs to the element, actionButton.focusin fires the
same expansion curve without requiring duplicate animation tags or
scripting layers.
Event Bubbling and Case Sensitivity Considerations
When working with focusin and focusout in
SMIL implementations across different rendering engines, several
technical nuances dictate behavior:
- Event Naming Conventions: Modern browsers implement
standard lower-case DOM event names (
focusin,focusout). Older SVG specifications referenced camelCase or legacy DOM events (DOMFocusIn,DOMFocusOut), but modern SVG rendering conforms to standard DOM Level 3 casing. - Bubbling Behavior: Unlike
focusandblur, which do not bubble up the DOM tree,focusinandfocusoutsupport event bubbling. This allows a parent group (<g>) to capture focus events triggered by any of its child shapes. - State Persistence: Using
fill="freeze"preserves the visual state of the animation until the correspondingendcondition (such asfocusoutormouseout) resets the timeline.