Press enter to smile

As you explore the syntax for SMIL animation you discover the begin attribute, a most powerful instruction to run animations with a delay.

<animate begin="1s" />

In sync with other animations.

<animate begin="trigger.begin" />

Or again following a specific event.

<animate begin="click" />

The documentation points to these and a handful more options, but at least one of them seems to promise more than it can actually deliver.

<animate begin="accessKey(t)" />

The premise is certainly enticing: add the accessKey function and you are able to consider user input. For the life of me, however, no combination of keys, no exploration of ISO standards lead me to trigger the animation with the matching character.

We can certainly try and implement the feature, but not with a single line.

But let’s take it one step at a time, and in the context of a most helpful example.

Key press

Let’s say you have a stylish, soft keyboard with a jolly enter key.

Be it the color or the shape, but the key just begs to be pressed. And with a cursor you can animate the visual with the precious click event.

<path>
	<animateTransform
		begin="click"
		attributeName="transform"
		type="translate"
		values="0 0; 0 1; 0 0"
		dur="0.35s"
	/>
</path>

Press the element and the shape moves briefly down, on top of the shadow. A small change with lots of depth.

If you are so inclined you might change the cursor and have the element stand out.

<path style="cursor: pointer">
	<!-- ...animateTransform -->
</path>

But with a mouse or a touch, that would be it.

With a keyboard we can trigger the animation with a different event, such as keydown. Add tabindex to have the element focusable — we’ll consider the implications of this action soon enough.

<path tabindex="0">
	<!-- ...animateTransform -->
</path>

Include the event alongside the previous click.

<path tabindex="0">
	<animateTransform
        begin="click; keydown"
        ...
    />
</path>

What do we have here? This is essentially a button. As we modified the tabindex attribute we need to signal the element as interactive.

<path
	tabindex="0"
    role="button"
    aria-label="Press a key to animate"
	>
	<!-- ...animateTransform -->
</path>

The role of button and the label do help to complete the feat, but the problem is immediately apparent.

“Press a key to animate”. Any key will do.

What we want, on the other hand, is to react to a specific key, the enter key if possible.

Failing the accessKey function, we need to be more elaborate and enter the world of Javascript.

Keyboard

Moving up from the single key, it is reasonable to shift the focus on a parent group element.

<g tabindex="0">
	<!-- ...keys -->
</g>

Indeed we don’t have one key, but a keyboard. Not a button, but a more complex menu.

<g 
	tabindex="0" 
	role="menu" 
	aria-label="Press enter to animate"
	>
	<!-- ...keys -->
</g>

menu, menubar. What is actually the most appropriate value for the role attribute? The moment we move away from semantic HTML elements to create custom interactions, the choice carries a lot of weight.

As the element is always visible, as conceptually you explore the keycaps from left to right, it might actually be preferable to choose the more specific option.

-role="menu"
+role="menubar"

The example might be trivial, but the decision is certainly important and worth a discussion, documentation in hand.

Accepting the value, we finally consider the logic of the case. Listen to the keydown event.

const keyboard = document.querySelector('g#keyboard');

keyboard.addEventListener('keydown', (e) => {
	// ...
});

In the body of the function then, but only if the key matches the desired value, access the animating element and trigger the animation with beginElement.

if (e.key === 'Enter') {
	keyboard
		.querySelector('animateTransform')
		.beginElement();
}

Elaborate? To be sure, it would have been much easier to rely on a single directive. But with JavaScript and a few carefully-chosen attributes we managed to extend the interaction to keyboard users.

The code is not excessively complex either.

And since we have access to JavaScript, since we have a function reacting to the event, we might just go ahead and spruce up the visual as the animation rolls out.

On click and a bright enter key.