Redon

一心的小屋

卡片发光效果

发布于 # React # CSS

CodePen 在线地址

HTML

<div id="app"></div>

CSS

:root {
  --backdrop: hsl(0 0% 60% / 0.12);
  --radius: 14;
  --border: 3;
  --backup-border: var(--backdrop);
  --size: 200;
}

article:first-of-type {
  --base: 80;
  --spread: 500;
  --outer: 1;
}
article:last-of-type {
  --outer: 1;
  --base: 220;
  --spread: 200;
}

*,
*:after,
*:before {
  box-sizing: border-box;
}
body {
  display: grid;
  place-items: center;
  min-height: 100vh;
  overflow: hidden;
  background: hsl(0 0% 4%);
}

.wrapper {
  position: relative;
}

article {
  aspect-ratio: 3 / 4;
  border-radius: calc(var(--radius) * 1px);
  width: 260px;
  position: relative;
  grid-template-rows: 1fr auto;
  box-shadow: 0 1rem 2rem -1rem black;
  padding: 1rem;
  display: grid;
  border: 1px solid hsl(0 0% 100% / 0.15);
  backdrop-filter: blur(calc(var(--cardblur, 5) * 1px));
  /* For demo purposes. Means you get the effect on mobile */
  touch-action: none;
}
main {
  display: flex;
  gap: 2rem;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  width: 120ch;
  max-width: calc(100vw - 2rem);
  position: relative;
}

/* Glow specific styles */
[data-glow] {
  --border-size: calc(var(--border, 2) * 1px);
  --spotlight-size: calc(var(--size, 150) * 1px);
  --hue: calc(var(--base) + (var(--xp, 0) * var(--spread, 0)));
  background-image: radial-gradient(
    var(--spotlight-size) var(--spotlight-size) at calc(var(--x, 0) * 1px) calc(
        var(--y, 0) * 1px
      ),
    hsl(
      var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(
          var(--lightness, 70) * 1%
        ) / var(--bg-spot-opacity, 0.1)
    ),
    transparent
  );
  background-color: var(--backdrop, transparent);
  background-size: calc(100% + (2 * var(--border-size))) calc(
      100% + (2 * var(--border-size))
    );
  background-position: 50% 50%;
  background-attachment: fixed;
  border: var(--border-size) solid var(--backup-border);
  position: relative;
  touch-action: none;
}

[data-glow]::before,
[data-glow]::after {
  pointer-events: none;
  content: "";
  position: absolute;
  inset: calc(var(--border-size) * -1);
  border: var(--border-size) solid transparent;
  border-radius: calc(var(--radius) * 1px);
  background-attachment: fixed;
  background-size: calc(100% + (2 * var(--border-size))) calc(
      100% + (2 * var(--border-size))
    );
  background-repeat: no-repeat;
  background-position: 50% 50%;
  mask: linear-gradient(transparent, transparent), linear-gradient(white, white);
  mask-clip: padding-box, border-box;
  mask-composite: intersect;
}

/* This is the emphasis light */
[data-glow]::before {
  background-image: radial-gradient(
    calc(var(--spotlight-size) * 0.75) calc(var(--spotlight-size) * 0.75) at
      calc(var(--x, 0) * 1px) calc(var(--y, 0) * 1px),
    hsl(
      var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(
          var(--lightness, 50) * 1%
        ) / var(--border-spot-opacity, 1)
    ),
    transparent 100%
  );
  filter: brightness(2);
}
/* This is the spotlight */
[data-glow]::after {
  background-image: radial-gradient(
    calc(var(--spotlight-size) * 0.5) calc(var(--spotlight-size) * 0.5) at calc(
        var(--x, 0) * 1px
      ) calc(var(--y, 0) * 1px),
    hsl(0 100% 100% / var(--border-light-opacity, 1)),
    transparent 100%
  );
}
[data-glow] > [data-glow]:not(:is(a, button)) {
  position: absolute;
  inset: 0;
  will-change: filter;
  opacity: var(--outer, 1);
}
[data-glow] > [data-glow]:not(:is(a, button)) {
  border-radius: calc(var(--radius) * 1px);
  border-width: calc(var(--border-size) * 20);
  filter: blur(calc(var(--border-size) * 10));
  background: none;
  pointer-events: none;
}
[data-glow] > [data-glow]:not(:is(a, button))::before {
  inset: -10px;
  border-width: 10px;
}
[data-glow] > [data-glow] {
  border: none;
}
[data-glow] :is(a, button) {
  border-radius: calc(var(--radius) * 1px);
  border: var(--border-size) solid transparent;
}
[data-glow] :is(a, button) [data-glow] {
  background: none;
}
[data-glow] :is(a, button) [data-glow]::before {
  inset: calc(var(--border-size) * -1);
  border-width: calc(var(--border-size) * 1);
}

article button {
  padding: 0.75rem 2rem;
  align-self: end;
  color: hsl(0 0% 80%);
}

button[data-glow] span {
  font-weight: bold;
  background-image: radial-gradient(
    var(--spotlight-size) var(--spotlight-size) at calc(var(--x, 0) * 1px) calc(
        var(--y, 0) * 1px
      ),
    hsl(
      var(--hue, 210) calc(var(--saturation, 100) * 1%) calc(
          var(--lightness, 70) * 1%
        ) / var(--bg-spot-opacity, 1)
    ),
    transparent
  );
  background-color: var(--backdrop, transparent);
  background-position: 50% 50%;
  background-attachment: fixed;
  background-clip: text;
  filter: brightness(1.5);
  color: transparent;
}

JavaScript

import React from "https://cdn.skypack.dev/react";
import { render } from "https://cdn.skypack.dev/react-dom";

const ROOT_NODE = document.querySelector("#app");

/**
 * Tiny hook that you can use where you need it
 */
const usePointerGlow = () => {
  const [status, setStatus] = React.useState(null);
  React.useEffect(() => {
    const syncPointer = ({ x: pointerX, y: pointerY }) => {
      const x = pointerX.toFixed(2);
      const y = pointerY.toFixed(2);
      const xp = (pointerX / window.innerWidth).toFixed(2);
      const yp = (pointerY / window.innerHeight).toFixed(2);
      document.documentElement.style.setProperty("--x", x);
      document.documentElement.style.setProperty("--xp", xp);
      document.documentElement.style.setProperty("--y", y);
      document.documentElement.style.setProperty("--yp", yp);
      setStatus({ x, y, xp, yp });
    };
    document.body.addEventListener("pointermove", syncPointer);
    return () => {
      document.body.removeEventListener("pointermove", syncPointer);
    };
  }, []);
  return [status];
};

const App = () => {
  const [status] = usePointerGlow();
  return (
    <main>
      <article data-glow>
        <span data-glow />
        <button data-glow>
          <span>Glow Up</span>
        </button>
      </article>
      <article data-glow>
        <span data-glow />
        <button data-glow>
          <span>Glow Up</span>
        </button>
      </article>
      <article data-glow>
        <span data-glow />
        <button data-glow>
          <span>Glow Up</span>
        </button>
      </article>
    </main>
  );
};

render(<App />, ROOT_NODE);