AI CSS Animations

Zoom to Cursor Image Hover

The photo zooms in toward the part you point at, and the zoom follows as you move, like a product gallery. A 3x3 grid of invisible cells moves transform-origin, so no JavaScript and no second image.

Also known as: product image zoom on hover, zoom image where mouse points, hover magnify image css.

HTML

<a class="hfx-image-zoom-to-cursor" href="#" aria-label="Open the full size photo">
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-cell" aria-hidden="true"></span>
  <span class="hfx-image-zoom-to-cursor-photo"></span>
  <span class="hfx-image-zoom-to-cursor-badge"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="6.5"/><path d="M20 20l-4.2-4.2M11 8.5v5M8.5 11h5"/></svg></span>
</a>

CSS

.hfx-image-zoom-to-cursor {
  --c: var(--hfx-color, #3b82f6);
  font-size: var(--hfx-size, 16px);
  position: relative;
  isolation: isolate;
  display: block;
  width: 16em;
  height: 10em;
  overflow: hidden;
  border-radius: 0.75em;
  cursor: zoom-in;
}
.hfx-image-zoom-to-cursor-cell {
  position: absolute;
  z-index: 3;
  top: 0;
  left: 0;
  width: calc(100% / 3);
  height: calc(100% / 3);
}
.hfx-image-zoom-to-cursor-cell:nth-child(3n + 2) { left: calc(100% / 3); }
.hfx-image-zoom-to-cursor-cell:nth-child(3n) { left: calc(200% / 3); }
.hfx-image-zoom-to-cursor-cell:nth-child(n + 4) { top: calc(100% / 3); }
.hfx-image-zoom-to-cursor-cell:nth-child(n + 7) { top: calc(200% / 3); }
.hfx-image-zoom-to-cursor-photo {
  position: absolute;
  inset: 0;
  background: radial-gradient(75% 42% at 18% 112%, color-mix(in srgb, var(--c) 45%, #0f172a) 99%, transparent),
    radial-gradient(66% 36% at 92% 108%, color-mix(in srgb, var(--c) 60%, #0f172a) 99%, transparent),
    conic-gradient(from 163deg at 6% 61%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 10% 57%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 14% 62%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 31% 63%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 35% 60%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 72% 64%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 77% 59%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 81% 62%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 163deg at 96% 60%, transparent 0, color-mix(in srgb, var(--c) 38%, #06101f) 0.4deg 33.6deg, transparent 34deg),
    conic-gradient(from 128deg at 70% 42%, transparent 0, color-mix(in srgb, var(--c) 78%, #1e293b) 0.4deg 103.6deg, transparent 104deg),
    conic-gradient(from 120deg at 92% 55%, transparent 0, color-mix(in srgb, var(--c) 78%, #1e293b) 0.4deg 119.6deg, transparent 120deg),
    conic-gradient(from 124deg at 28% 38%, transparent 0, color-mix(in srgb, var(--c) 45%, #fff) 0.4deg 111.6deg, transparent 112deg),
    conic-gradient(from 126deg at 50% 52%, transparent 0, color-mix(in srgb, var(--c) 45%, #fff) 0.4deg 107.6deg, transparent 108deg),
    radial-gradient(circle at 70% 26%, #fff 0 0.75em, rgb(255 255 255 / 0.45) 0.85em, transparent 3em),
    linear-gradient(color-mix(in srgb, var(--c) 60%, #fff), color-mix(in srgb, var(--c) 14%, #fff7ed) 75%);
  transform-origin: 50% 50%;
  transition: transform 0.45s cubic-bezier(0.25, 0.8, 0.25, 1), transform-origin 0.45s cubic-bezier(0.25, 0.8, 0.25, 1);
}
.hfx-image-zoom-to-cursor-badge {
  position: absolute;
  right: 0.6em;
  bottom: 0.6em;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2em;
  height: 2em;
  border-radius: 50%;
  background: rgb(255 255 255 / 0.9);
  color: var(--c);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.hfx-image-zoom-to-cursor-badge svg {
  width: 1.1em;
  height: 1.1em;
}
.hfx-image-zoom-to-cursor:is(:hover, :focus-visible) .hfx-image-zoom-to-cursor-photo {
  transform: scale(2);
}
.hfx-image-zoom-to-cursor:is(:hover, :focus-visible) .hfx-image-zoom-to-cursor-badge {
  opacity: 0;
  transform: scale(0.6);
}
.hfx-image-zoom-to-cursor-cell:nth-child(1):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 12% 12%; }
.hfx-image-zoom-to-cursor-cell:nth-child(2):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 50% 12%; }
.hfx-image-zoom-to-cursor-cell:nth-child(3):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 88% 12%; }
.hfx-image-zoom-to-cursor-cell:nth-child(4):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 12% 50%; }
.hfx-image-zoom-to-cursor-cell:nth-child(5):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 50% 50%; }
.hfx-image-zoom-to-cursor-cell:nth-child(6):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 88% 50%; }
.hfx-image-zoom-to-cursor-cell:nth-child(7):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 12% 88%; }
.hfx-image-zoom-to-cursor-cell:nth-child(8):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 50% 88%; }
.hfx-image-zoom-to-cursor-cell:nth-child(9):hover ~ .hfx-image-zoom-to-cursor-photo { transform-origin: 88% 88%; }

@media (prefers-reduced-motion: reduce) {
  .hfx-image-zoom-to-cursor, .hfx-image-zoom-to-cursor *, .hfx-image-zoom-to-cursor::before, .hfx-image-zoom-to-cursor::after, .hfx-image-zoom-to-cursor *::before, .hfx-image-zoom-to-cursor *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Customizing this snippet

  • Color: set --hfx-color on the element or a parent. Currently previewing #3b82f6.
  • Base size: set --hfx-size. Every other dimension is in em, so the whole thing scales together. Currently previewing 16px.
  • Speed: change the duration on the transition or animation shorthand. Staggered delays are relative, so they keep their rhythm.
  • Reduced motion: the rule at the end of the CSS turns the motion off for people who asked their OS for less of it. Keep it.

Similar hover effects