Hover and press
The examples that import shape-aware-morph use the npm package. It is in progress. See Install.
An icon can react to the pointer. Set hover and the icon plays when the pointer enters its button, or when the button gets focus. Set press and the icon plays when the pointer goes down (a mouse press or a tap), and when the user presses Enter or Space. The button's click still fires. The icon looks the same at the end. It never turns into another icon, unless you ask for that (see "Change the icon on click").
<button type="button" aria-label="Notifications"><morph-icon name="bell" hover="grow"></morph-icon></button>
<button type="button" aria-label="Delete"><morph-icon name="trash" press="burst"></morph-icon></button>
Hover over the first icon and click the second one.
<button type="button" aria-label="Notifications"><morph-icon name="bell" size="40" hover="grow"></morph-icon></button>
<button type="button" aria-label="Delete"><morph-icon name="trash" size="40" press="burst"></morph-icon></button>
Every style works on any icon of any family. The engine reads the shape, so the motion fits the icon. No point goes more than 3 units outside the 24 box. The stroke width never changes. Nothing fades. Strokes that touch each other stay joined, except in cascade (it draws the strokes in order) and in the ripple move of wiggle.
Attributes
| Attribute | Values | Default |
|---|---|---|
hover | wiggle, grow, bend, bold, cascade, move, or none | none |
press | pop, squash, squat-jump, burst, or none | none |
hover-strength, press-strength | A number from 0.5 to 3. | 1.5 |
hover-duration, press-duration | Milliseconds. | The style's own time. |
The old names still work: hover="alive" is wiggle, and press="anticipate-jump" is squat-jump. The default strength of alive is now 1.5 (in 0.1.0 it was 1). Set hover-strength="1" for the old size. A value that is not a style name does nothing. To turn a style off, set it to none. If you remove an attribute, the element keeps the last value.
Hover styles
Hover over each icon. On a touch screen, tap it.
wiggle
The engine picks one of four moves from the shape of the icon: radiate, ripple, swing, or pop. The bell swings, the sun radiates, and the heart pops. It lasts 600 ms.
<button type="button" aria-label="Notifications"><morph-icon name="bell" size="40" hover="wiggle"></morph-icon></button>
<button type="button" aria-label="Brightness"><morph-icon name="sun" size="40" hover="wiggle"></morph-icon></button>
<button type="button" aria-label="Favorite"><morph-icon name="heart" size="40" hover="wiggle"></morph-icon></button>
grow
The parts move out from the middle of the icon. The farther a part is from the middle, the more it moves. Then they spring back. It lasts 400 ms.
<button type="button" aria-label="Home"><morph-icon name="house" size="40" hover="grow"></morph-icon></button>
<button type="button" aria-label="Search"><morph-icon name="search" size="40" hover="grow"></morph-icon></button>
<button type="button" aria-label="Profile"><morph-icon name="user" size="40" hover="grow"></morph-icon></button>
bend
The lines bend like soft wire, and spring back. It lasts 400 ms.
<button type="button" aria-label="Mail"><morph-icon name="mail" size="40" hover="bend"></morph-icon></button>
<button type="button" aria-label="Settings"><morph-icon name="settings" size="40" hover="bend"></morph-icon></button>
<button type="button" aria-label="Star"><morph-icon name="star" size="40" hover="bend"></morph-icon></button>
bold
The strokes get heavier and stay heavy while the pointer is over the icon. When the pointer leaves, they go back from where they are. The swell takes 250 ms. On a touch screen, tap the icon to hold it and tap it again to let go.
<button type="button" aria-label="Favorite"><morph-icon name="heart" size="40" hover="bold"></morph-icon></button>
<button type="button" aria-label="Bookmark"><morph-icon name="bookmark" size="40" hover="bold"></morph-icon></button>
<button type="button" aria-label="Share"><morph-icon name="share" size="40" hover="bold"></morph-icon></button>
cascade
The strokes erase along their own path and draw again, one after another. The body comes first and the parts follow. At least a part of every stroke stays drawn, so the icon is never empty. It lasts 500 ms.
<button type="button" aria-label="Send"><morph-icon name="send" size="40" hover="cascade"></morph-icon></button>
<button type="button" aria-label="Trash"><morph-icon name="trash" size="40" hover="cascade"></morph-icon></button>
<button type="button" aria-label="Play"><morph-icon name="play" size="40" hover="cascade"></morph-icon></button>
move
The icon hangs from its top (a bell) or stands on its base (a house, a trash can). It sways with the speed of the pointer, live. Move the pointer across the icon slowly and fast. When the pointer leaves, the icon swings back and settles quickly. The default time is 900 ms.
<button type="button" aria-label="Notifications"><morph-icon name="bell" size="40" hover="move"></morph-icon></button>
<button type="button" aria-label="Trash"><morph-icon name="trash" size="40" hover="move"></morph-icon></button>
<button type="button" aria-label="Home"><morph-icon name="house" size="40" hover="move"></morph-icon></button>
Press styles
Press each icon with the mouse, or tap it.
pop
The icon grows a little and settles. It lasts 300 ms.
<button type="button" aria-label="Like"><morph-icon name="heart" size="40" press="pop"></morph-icon></button>
<button type="button" aria-label="Save"><morph-icon name="bookmark" size="40" press="pop"></morph-icon></button>
squash
The icon squashes down and springs back with a small stretch. It lasts 300 ms.
<button type="button" aria-label="Like"><morph-icon name="star" size="40" press="squash"></morph-icon></button>
<button type="button" aria-label="Mail"><morph-icon name="mail" size="40" press="squash"></morph-icon></button>
squat-jump
The icon squats, jumps, and lands with a small squash. It lasts 500 ms.
<button type="button" aria-label="Follow"><morph-icon name="user-plus" size="40" press="squat-jump"></morph-icon></button>
<button type="button" aria-label="Play"><morph-icon name="play" size="40" press="squat-jump"></morph-icon></button>
burst
The parts fly out from the middle of the icon and snap back. It lasts 500 ms.
<button type="button" aria-label="Delete"><morph-icon name="trash" size="40" press="burst"></morph-icon></button>
<button type="button" aria-label="Settings"><morph-icon name="settings" size="40" press="burst"></morph-icon></button>
Change the icon on click
A press can change the icon. The new icon morphs while the press plays, so the click and the change are one motion. Call pressTo(name) on the element. Or set the name attribute while the press runs. The press starts on pointer down, and the click comes a moment later, so setting name in a click handler joins the press that plays.
<button type="button" id="delete-demo" aria-label="Delete"><morph-icon name="trash" size="40" press="burst"></morph-icon></button>
<script type="module">
const button = document.getElementById('delete-demo');
const icon = button.querySelector('morph-icon');
let done = false;
button.addEventListener('click', () => {
done = !done;
icon.pressTo(done ? 'check' : 'trash');
});
</script>
The press style comes from the press attribute. Without it, pressTo uses squash. The Promise gives true when the run ends.
Strength and time
hover-strength and press-strength set how far the motion goes. The range is 0.5 to 3. The default is 1.5. The engine keeps every style inside the 3 unit limit, so a high strength on a big icon gives a little less than you ask for.
<button type="button" aria-label="Mail"><morph-icon name="mail" size="40" hover="bend" hover-strength="0.5"></morph-icon></button>
<button type="button" aria-label="Mail"><morph-icon name="mail" size="40" hover="bend" hover-strength="1.5"></morph-icon></button>
<button type="button" aria-label="Mail"><morph-icon name="mail" size="40" hover="bend" hover-strength="3"></morph-icon></button>
hover-duration and press-duration set the time in milliseconds. Each style has its own default time (see the lists above).
Focus, touch and reduced motion
- Hover also plays when the button gets keyboard focus. The handler goes on the nearest
button,a,input,select,textarea, or element withtabindexorrole="button". If there is none, it goes on the icon. - Press also plays on Enter and Space.
- A touch screen has no hover. A tap plays the hover style once. A tap on a
boldicon holds it until a second tap or a tap elsewhere. - With
prefers-reduced-motion: reduce, hover and press do nothing. A press that changes the icon changes it at once. - A morph that runs wins over a hover. A press replaces a hover that runs.
- When the pointer leaves in the middle of a hover, the icon goes back to rest from where it is. It does not jump. When the pointer comes back, it goes on from where it is. Two styles finish their short run instead:
wiggleandcascade.
In code
Each style is a plan with frame(t), like planMorph. At t = 0 and t = 1 the plan draws the rest icon. With to, it draws the old icon at t = 0 and the new icon at t = 1.
import { registerLucide, planAlive, planPress, createSway } from 'shape-aware-morph';
await registerLucide();
console.log(planAlive('house', { style: 'grow' }).frame(0.3).length > 0);
console.log(planPress('trash', { style: 'burst', to: 'check' }).frame(0.5).length > 0);
const sway = createSway('bell'); // the live sway of hover="move"
sway.push(0.8); // the pointer came in fast, to the right
console.log(sway.frame(16).length > 0);
createSway(name) runs frame by frame. Call sway.setInput(vx) while the pointer moves over the icon (vx from -1 to 1), sway.release() when it leaves, and sway.frame(now) on every animation frame until sway.atRest is true. The element does this for you.
planAlive(name, { style, strength }) takes a hover style. planPress(name, { style, strength, to }) takes a press style. The export HOVER_STYLES lists the hover styles, and PRESS_STYLES lists the press styles. Both are in the npm package. The script tag build also has them on window.shapcn, together with planPress and createSway.