Surprising hover and scroll

Swap the stock opacity dip for CSS hover and scroll that surprise.

From frontend-design, Motion: hover and scroll states that surprise.

Hover gallery

Hover, tap or Tab each element in both panes.

.card, .card figcaption, .link::after { transition: transform 180ms var(--ease-out); }
.card figcaption { transform: translateY(100%); }
.link::after { transform: scaleX(0); transform-origin: right; }
.img::before, .img::after { opacity: 0; transition: opacity 180ms var(--ease-out); }

@media (hover: hover) and (pointer: fine) {
  .card:hover { transform: perspective(700px) rotateY(-8deg) translateY(-4px); transition-duration: 250ms; }
  .card:hover figcaption { transform: none; }
  .link:hover::after { transform: scaleX(1); transform-origin: left; }
  .img:hover::before, .img:hover::after { opacity: 1; }
}

Scroll reveal

Scroll inside each pane; change the wipe.

StaticFlat

Scroll

01

Slack water

Twenty quiet minutes when the tide turns. Launch here.

02

The ebb

Water drains past the breakwater at two knots. Keep left.

03

Low water

The sandbar shows. Anything drawing a metre waits.

04

The flood

It comes back faster than it left. Moor before it does.

05

High water

06:42 tomorrow, 4.1 m. The quay steps disappear.

no motion
Clip-path wipeScroll-driven

Scroll

01

Slack water

Twenty quiet minutes when the tide turns. Launch here.

02

The ebb

Water drains past the breakwater at two knots. Keep left.

03

Low water

The sandbar shows. Anything drawing a metre waits.

04

The flood

It comes back faster than it left. Moor before it does.

05

High water

06:42 tomorrow, 4.1 m. The quay steps disappear.

animation-range: entry 0% entry 70%
@supports (animation-timeline: view()) {
  .reveal {
    animation: wipe linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 70%;
  }
}
@keyframes wipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0); } }

Same hover, CSS vs JS

Hover or tap both tiles, then Block script and retry.

JS listenersFragile
mouseenter / mouseleave0 calls
Pure CSSDependable
:hover, :focus-visible
.tile { transition: transform 180ms var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .tile:hover { transform: translateY(-3px) rotate(-1.5deg); transition-duration: 250ms; }
}
.tile:focus-visible { transform: translateY(-3px) rotate(-1.5deg); transition-duration: 0ms; }

Why it matters, pros and cons

Why it matters

Hover and scroll are feedback people asked for, so they can carry character.

Pros

  • Memorable without slowing the task.
  • Works without JavaScript, covers focus too.
  • Scroll-driven motion needs no listeners.

Cons

  • Touch has no hover; hidden content needs another way.
  • Surprise wears off on things used daily.
  • animation-timeline needs an @supports fallback.