shapcnGitHub

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

AttributeValuesDefault
hoverwiggle, grow, bend, bold, cascade, move, or nonenone
presspop, squash, squat-jump, burst, or nonenone
hover-strength, press-strengthA number from 0.5 to 3.1.5
hover-duration, press-durationMilliseconds.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

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.

Markdown version