HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ashleybrookecs Smudge Revealer | Codegrid</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<section class="hero">
<div class="hero-content-foreground">
<h1>Dig in</h1>
</div>
<div class="hero-content-background">
<h3>
The things worth finding are never on the surface. They live in the
parts you almost scrolled past.
</h3>
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="none"
class="smudge-revealer"
>
<defs>
<filter id="smudge-goo">
<feGaussianBlur in="SourceGraphic" stdDeviation="25" />
<feColorMatrix
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 60 -14"
/>
</filter>
</defs>
<mask id="smudge-mask">
<g class="smudge-blobs" filter="url(#smudge-goo)"></g>
</mask>
</svg>
</section>
<script type="module" src="/script.js"></script>
</body>
</html>
js
import gsap from "gsap";
const config = {
smoothing: 0.1,
movementThreshold: 0.01,
sizeFromSpeed: 0.2,
expandMultiplier: 2,
expandTime: 2,
expandEase: "power1.inOut",
dissolveStart: 2,
dissolveTime: 3,
dissolveEase: "power3.in",
};
const heroSection = document.querySelector(".hero");
const smudgeSVG = document.querySelector(".smudge-revealer");
const smudgeContainer = document.querySelector(".smudge-blobs");
const pointer = { x: 0, y: 0 };
const smoothPointer = { x: 0, y: 0 };
let hasStarted = false;
function onPointerMove(x, y) {
if (!hasStarted) {
pointer.x = smoothPointer.x = x;
pointer.y = smoothPointer.y = y;
hasStarted = true;
return;
}
pointer.x = x;
pointer.y = y;
}
heroSection.addEventListener("mousemove", function (e) {
onPointerMove(e.pageX, e.pageY);
});
heroSection.addEventListener(
"touchstart",
function (e) {
e.preventDefault();
onPointerMove(e.touches[0].pageX, e.touches[0].pageY);
},
{ passive: false },
);
heroSection.addEventListener(
"touchmove",
function (e) {
e.preventDefault();
onPointerMove(e.touches[0].pageX, e.touches[0].pageY);
},
{ passive: false },
);
function matchSVGToViewport() {
smudgeSVG.style.width = window.innerWidth + "px";
smudgeSVG.style.height = window.innerHeight + "px";
}
matchSVGToViewport();
window.addEventListener("resize", matchSVGToViewport);
function stampSmudgeAt(x, y, radius) {
const circle = document.createElementNS(
"http://www.w3.org/2000/svg",
"circle",
);
circle.setAttribute("cx", x);
circle.setAttribute("cy", y);
circle.setAttribute("r", radius);
circle.setAttribute("fill", "#fff");
smudgeContainer.prepend(circle);
const animatedRadius = { current: radius };
const timeline = gsap.timeline({
onUpdate() {
circle.setAttribute("r", Math.max(0, animatedRadius.current));
},
onComplete() {
timeline.kill();
circle.remove();
},
});
timeline.to(animatedRadius, {
current: radius * config.expandMultiplier,
duration: config.expandTime,
ease: config.expandEase,
});
timeline.to(
animatedRadius,
{
current: 0,
duration: config.dissolveTime,
ease: config.dissolveEase,
},
config.dissolveStart,
);
}
function update() {
if (hasStarted) {
smoothPointer.x += (pointer.x - smoothPointer.x) * config.smoothing;
smoothPointer.y += (pointer.y - smoothPointer.y) * config.smoothing;
const speed = Math.hypot(
pointer.x - smoothPointer.x,
pointer.y - smoothPointer.y,
);
if (speed > config.movementThreshold) {
stampSmudgeAt(
smoothPointer.x,
smoothPointer.y,
speed * config.sizeFromSpeed,
);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
h1,
h3 {
text-transform: uppercase;
font-family: "Hanson", sans-serif;
line-height: 0.9;
}
h1 {
font-size: clamp(5rem, 22.5vw, 30rem);
}
h3 {
font-size: clamp(3rem, 5vw, 6rem);
}
.hero {
position: relative;
width: 100%;
height: 100svh;
overflow: hidden;
}
.hero-content-background,
.hero-content-foreground {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 2rem;
text-align: center;
user-select: none;
}
.hero-content-foreground {
display: flex;
justify-content: center;
align-items: flex-end;
background-color: #2a2b2a;
color: #edf2ed;
}
.hero-content-background {
display: flex;
justify-content: center;
align-items: center;
background-color: #cbd4c2;
color: #323332;
mask: url(#smudge-mask);
-webkit-mask: url(#smudge-mask);
}
.smudge-revealer {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
获取源码: CODE
下载数:1人次, 文件大小: 4.7 KB, 上传日期: 2026年-5 月-11日
28 人查阅


