After reading Stokry's post on his typing animation with CSS I took the opportunity to use this effect and improve it.
The Idea
At Mr. Henry we're currently working on the full website of our client theAddresses. Our designers came up with the idea to show some Portuguese words in sentences, when you click it the explanation appears.
The Goal
We try our best at respecting the a11y rules as much as possible so for this component we do make no exceptions.
The component should:
- be understood by screen readers;
- respect users their preferences on reduced motion;
- is usable for keyboard users.
This component should be flexible with our CMS editor and it would be great to make it work without any JavaScript.
The Code: HTML
First of all the html, note that we're working with twig.
{% set explainer_id = function('uniqid', 'explainer-', false) %}
<span
aria-label="'{{ text }}' which means '{{ explanation }}'"
class="explainer"
>
<input
aria-hidden="true"
class="explainer__checkbox"
id="{{ explainer_id }}"
name="{{ explainer_id }}"
type="checkbox"
>
<label
aria-hidden="true"
class="explainer__label"
data-text="{{ text }}"
data-explanation="{{ explanation }}"
for="{{ explainer_id }}"
>
<span class="explainer__text">
{{ text }}
</span>
<span class="explainer__explanation">
{{ explanation }}
</span>
</label>
</span>
HTML — Interactivity
A very simple yet powerful hack in HTML/CSS is to make use of a checkbox and listen to it :checked
state in CSS to make your components interactive without a single line of JS.
HTML — A11y
We've made this clear and simple for screen readers by adding an aria-label with the copy:
'a Portuguese word' which means 'the explanation'
Our child elements have an aria-hidden
attribute so the user can't be bothered or confused with other content.
Because we're using a checkbox, this element will be accessible for keyboard users.
HTML — IDs
IDs always should be unique in a HTML document, this is why we're using the PHP function uniqid.
Later on, this will come in handy to select the specific component with css.
The Code: Inline Style
Secondly we're going to add the inline style to our twig code:
{% set msPerChar = 32 %}
{% set msExtraDelay = 320 %}
<style>
@media (prefers-reduced-motion: no-preference) {
#{{ explainer_id }} ~ .explainer__label::after,
#{{ explainer_id }} ~ .explainer__label::before {
transition: max-width 0s linear {{ msPerChar * explanation|length }}ms;
}
#{{ explainer_id }}:checked ~ .explainer__label::after,
#{{ explainer_id }}:checked ~ .explainer__label::before {
transition: max-width 0s linear {{ msPerChar * text|length }}ms;
}
#{{ explainer_id }} ~ .explainer__label .explainer__text {
transition: width {{ msPerChar * text|length }}ms steps({{ text|length }});
transition-delay: {{ (msPerChar * explanation|length) + msExtraDelay }}ms;
}
#{{ explainer_id }} ~ .explainer__label .explainer__explanation {
transition: width {{ msPerChar * explanation|length }}ms steps({{ explanation|length }});
transition-delay: 0s;
}
#{{ explainer_id }}:checked ~ .explainer__label .explainer__text {
transition-delay: 0s;
}
#{{ explainer_id }}:checked ~ .explainer__label .explainer__explanation {
transition-delay: {{ (msPerChar * text|length) + msExtraDelay }}ms;
}
}
</style>
TWIG — msPerChar
& msExtraDelay
To make the animation as consistent as possible, we're using twig variables and calculate our delays together with the string lengths.
CSS — Animation
As you can see we're making use of a before and after element, more on that further in this post.
The text and explanation width is animated in steps corresponding with the string length.
The Code: CSS
.explainer {
border-bottom: 2px dashed;
display: inline-block;
white-space: nowrap;
&:hover {
border-bottom-style: solid;
}
}
.explainer__checkbox {
height: 0;
left: 0;
outline: none;
position: absolute;
top: 0;
visibility: hidden;
width: 0;
}
.explainer__label {
cursor: help;
}
.explainer__text,
.explainer__explanation {
display: inline-block;
overflow: hidden;
position: relative;
}
.explainer__checkbox:checked ~ .explainer__label .explainer__text,
.explainer__checkbox:not(:checked) ~ .explainer__label .explainer__explanation {
/* Visually hide elements on older browsers or for users who prefer no animations. */
width: 0;
}
@media (prefers-reduced-motion: no-preference) {
.explainer__label {
position: relative;
}
/* Before and After pseudo element will be used as placeholders of the text */
.explainer__label::after,
.explainer__label::before {
display: inline-block;
opacity: 0;
overflow: hidden;
width: auto;
}
.explainer__label::before {
content: attr(data-text);
max-width: 420px;
.explainer__checkbox:checked ~ & {
/* Hide original placeholder text */
max-width: 0;
}
}
.explainer__label::after {
content: attr(data-explanation);
max-width: 0;
.explainer__checkbox:checked ~ & {
/* Show explanation placeholder text */
max-width: 420px;
}
}
.explainer__text,
.explainer__explanation {
/* Position the actual text absolute */
left: 0;
position: absolute;
top: 50%;
transform: translateY(-50%) translateZ(0);
width: 100%;
}
.explainer__text::after,
.explainer__explanation::after {
/* To add a more typing look we're adding a cursor here */
background-color: transparent;
content: "";
height: 100%;
position: absolute;
right: 0;
top: 0;
width: 2px;
.explainer:focus &,
.explainer:hover &,
.explainer__checkbox:focus ~ .explainer__label & {
animation: BLINKING 320ms step-end infinite alternate;
}
}
}
@keyframes BLINKING {
50% {
background-color: currentColor;
}
}
CSS — prefers-reduced-motion: no-preference
To respect users who do prefer reduced motion we're going to show and hide the text and explanation without any transition.
Only if the preferences are set to no-preference
we're going to add some animations and transitions.
CSS — Pseudo Elements with data attributes
To make the animation snappy and smooth we're going to add pseudo elements with the text and explanation. This so we can toggle the width and be able to smoothly animate the width of the text and explanation spans from 0 to 100%.
CSS — :hover, :focus & :checked
animations
As a hover state we change the border-style from dashed
to solid
. Bonus: we've added a blinking animation to give it a more typewriter look and feel.
When the keyboard users are focussed on the checkbox, the blinking. When they hit space, the checkbox sets itself to checked and the animation initiates.
When the checkbox is :checked
we will change the width of the text and explanation span.
CSS — pitfalls
The element needs white-space: nowrap
and a max-width. This means the element can only be used for single line short sentences.
Codepen Example
The animation
Sidenote
I also want to point out this animation could've been more accurate when using a mono typeface and use ch units like Lea Verou did come up with.
Top comments (1)
Having spent a lot of time developing web services and web apps, I can say from experience that typing effects create extra interest in your landing pages. how to undo a love spell