这段代码实现了一个具有动态视觉效果和交互功能的导航栏页面,通过 CSS 的高级特性和 JavaScript 的动态生成逻辑,营造出独特的视觉体验。
大家复制代码时,可能会因格式转换出现错乱,导致样式失效。建议先少量复制代码进行测试,若未能解决问题,私信回复源码两字,我会发送完整的压缩包给你。
演示效果
HTML&CSS
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>公众号关注:前端Hardy</title>
<style>
.nav {
display: flex;
position: relative;
transform: translate3d(0, 0, 0.01px);
opacity: 0.999;
background: hsl(205deg 0% 0% / 0.1);
backdrop-filter: blur(9px) brightness(1.1);
border-radius: 100vw;
padding: 0.4em;
box-shadow:
0 4px 20px hsl(205deg 50% 30% / 0.15),
0 4px 10px hsl(205deg 30% 10% / 0.075),
inset 0 -4px 15px 6px hsl(205deg 70% 90% / 0.2),
inset 0 -2px 5px hsl(205deg 70% 90% / 0.15),
inset 0 -1px 1px hsl(205deg 70% 90% / 0.4),
inset 0 10px 15px hsl(205deg 30% 10% / 0.2),
inset 0 1px 2px hsl(205deg 70% 90% / 0.3);
}
.nav:before {
content: "";
inset: 0;
position: absolute;
mask-size: 55%;
border-radius: 100vw;
z-index: 1;
backdrop-filter: blur(7px) brightness(1.05);
background: hsl(205deg 0% 100% / 0.1);
}
.nav:after {
content: "";
inset: 0;
position: absolute;
mask-mode: luminance;
mask-size: 50%;
mask-repeat: repeat;
border-radius: 100vw;
z-index: 2;
backdrop-filter: blur(5px) brightness(0.95);
box-shadow:
inset 0 -4px 15px 5px hsl(205deg 70% 90% / 0.3),
inset 0 -2px 5px hsl(205deg 70% 90% / 0.15),
inset 0 -1px 1px hsl(205deg 70% 90% / 0.4),
inset 0 1px 2px hsl(205deg 70% 90% / 0.3);
background: hsl(205deg 0% 20% / 0.05);
}
.nav ul {
display: flex;
gap: 5em;
list-style: none;
padding: 0 1em;
margin: 0;
position: relative;
z-index: 3;
color: white;
text-shadow: 0 1px 1px hsl(205deg 30% 10% / 0.2);
}
.nav ul li {
padding: 0.6em 1em;
border-radius: 100vw;
position: relative;
transition: all 1.8s var(--linear-ease) 0.2s, box-shadow 0.3s ease;
color: white;
cursor: pointer;
box-shadow: 0 0 0.5px 1.5px transparent;
&:focus-within:has(:focus-visible) {
box-shadow: 0 0 0.5px 1.5px white;
}
}
.nav ul li:after {
content: "";
position: absolute;
inset: 0;
border-radius: 100vw;
background: white;
opacity: 0;
scale: 0;
transition: all 2s var(--linear-ease) 0.2s;
z-index: -1;
}
.nav ul li.active {
color: black;
text-shadow: none;
}
.nav ul li.active:after {
opacity: 1;
scale: 1;
}
.effect {
position: fixed;
left: 0;
top: 0;
width: 0px;
height: 0px;
opacity: 1;
pointer-events: none;
display: grid;
place-items: center;
z-index: 1;
}
.effect.text {
color: white;
z-index: 1;
transition: color 0s ease;
}
.effect.text.active {
color: black;
transition: color 1.8s var(--linear-ease) 0.2s;
}
.effect.filter {
filter: blur(7px) contrast(20) blur(0);
mix-blend-mode: lighten;
position: absolute;
}
.effect.filter::before {
content: "";
position: absolute;
inset: -75px;
z-index: -2;
background: black;
}
.effect.filter::after {
content: "";
position: absolute;
inset: 0px;
background: white;
scale: 0;
opacity: 0;
z-index: -1;
border-radius: 100vw;
}
.effect.active:after {
animation: pill 2s var(--linear-ease) 0.2s both;
}
.particles {
position: absolute;
inset: 0;
transform: translate3d(0, 0, 0px);
}
.particle,
.point {
display: block;
opacity: 0;
width: 20px;
height: 20px;
border-radius: 100%;
transform-origin: center;
}
.particle {
--time: 5s;
opacity: 0;
position: absolute;
top: calc(50% - 8px);
left: calc(50% - 8px);
animation: particle calc(var(--time)) ease 1 -350ms;
}
.point {
background: var(--color);
opacity: 1;
animation: point calc(var(--time)) ease 1 -350ms;
}
@keyframes particle {
0% {
transform: rotate(0deg) translate(calc(var(--start-x) * 1), calc(var(--start-y) * 1));
opacity: 1;
animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45);
}
70% {
transform: rotate(calc(var(--rotate) * 0.5)) translate(calc(var(--end-x) * 1.2), calc(var(--end-y) * 1.2));
opacity: 1;
animation-timing-function: ease;
}
85% {
transform: rotate(calc(var(--rotate) * 0.66)) translate(calc(var(--end-x) * 1), calc(var(--end-y) * 1));
opacity: 1;
}
100% {
transform: rotate(calc(var(--rotate) * 1.2)) translate(calc(var(--end-x) * 0.5), calc(var(--end-y) * 0.5));
opacity: 1;
}
}
@keyframes point {
0% {
scale: 0;
opacity: 0;
animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45);
}
25% {
scale: calc(var(--scale) * 0.25);
}
38% {
opacity: 1;
}
65% {
scale: var(--scale);
opacity: 1;
animation-timing-function: ease;
}
85% {
scale: var(--scale);
opacity: 1;
}
100% {
scale: 0;
opacity: 0;
}
}
@keyframes pill {
to {
scale: 1;
opacity: 1;
}
}
@keyframes pillOff {
from {
scale: 1;
opacity: 1;
}
}
:root {
--linear-ease: linear(0, 0.068, 0.19 2.7%, 0.804 8.1%, 1.037, 1.199 13.2%, 1.245, 1.27 15.8%, 1.274, 1.272 17.4%, 1.249 19.1%, 0.996 28%, 0.949, 0.928 33.3%, 0.926, 0.933 36.8%, 1.001 45.6%, 1.013, 1.019 50.8%, 1.018 54.4%, 1 63.1%, 0.995 68%, 1.001 85%, 1);
}
body,
html,
#app {
height: 100%;
margin: 0;
padding: 0;
outline: none;
color: inherit;
}
#app {
font-size: 20px;
display: grid;
grid-template-rows: 1fr 2fr 0.5fr;
place-items: center;
background-color: #212121;
display: flex;
align-items: center;
justify-content: center;
background-image: url(https://images.pexels.com/photos/2387532/pexels-photo-2387532.jpeg?auto=compress&cs=tinysrgb&w=1800&dpr=2);
background-size: cover;
}
#app {
& a,
& a:hover,
& a:active,
& a:focus {
color: inherit;
outline: none;
text-decoration: none;
}
}
</style>
</head>
<body>
<main id="app">
<nav class="nav">
<ul>
<li><a href="#">home</a></li>
<li><a href="#">about</a></li>
<li><a href="#">feature</a></li>
</ul>
</nav>
<span class="effect filter">
</span>
<span class="effect text">
about
</span>
</main>
<script>
const nav = document.querySelector("nav");
const effectEl = document.querySelector(".effect.filter");
const textEl = document.querySelector(".effect.text");
let animationTime = 600;
let pCount = 15;
const minDistance = 20;
const maxDistance = 42;
const maxRotate = 75;
const colors = [1, 2, 3, 1, 2, 3, 1, 4];
const timeVariance = 300;
function noise(n = 1) {
return n / 2 - Math.random() * n;
}
function getXY(distance, pointIndex, totalPoints) {
const x = (distance) * Math.cos(((360 + noise(8)) / totalPoints * pointIndex) * Math.PI / 180);
const y = (distance) * Math.sin(((360 + noise(8)) / totalPoints * pointIndex) * Math.PI / 180);
return [x, y];
}
function makeParticles($el) {
const d = [90, 10];
const r = 100;
const bubbleTime = animationTime * 2 + timeVariance;
$el.style.setProperty('--time', bubbleTime + 'ms');
for (let i = 0; i < pCount; i++) {
const t = animationTime * 2 + noise(timeVariance * 2);
const p = createParticle(i, t, d, r);
const $place = $el;
if ($place) {
$place.classList.remove('active');
setTimeout(() => {
const $particle = document.createElement('span');
const $point = document.createElement('span');
$particle.classList.add('particle');
$particle.style = `
--start-x: ${p.start[0]}px;
--start-y: ${p.start[1]}px;
--end-x: ${p.end[0]}px;
--end-y: ${p.end[1]}px;
--time: ${p.time}ms;
--scale: ${p.scale};
--color: var( --color-${p.color}, white );
--rotate: ${p.rotate}deg;
`;
$point.classList.add('point');
$particle.append($point);
$place.append($particle);
requestAnimationFrame(() => {
$place.classList.add('active');
})
setTimeout(() => {
try {
$place.removeChild($particle);
} catch (e) {
}
}, t);
}, 30);
};
}
}
function createParticle(i, t, d, r) {
let rotate = noise(r / 10);
let minDistance = d[0];
let maxDistance = d[1];
return {
start: getXY(minDistance, pCount - i, pCount),
end: getXY(maxDistance + noise(7), pCount - i, pCount),
time: t,
scale: 1 + noise(0.2),
color: colors[Math.floor(Math.random() * colors.length)],
rotate: rotate > 0 ? (rotate + r / 20) * 10 : (rotate - r / 20) * 10
}
}
function updateEffectPosition(element) {
const pos = element.getBoundingClientRect();
const styles = {
left: `${pos.x}px`,
top: `${pos.y}px`,
width: `${pos.width}px`,
height: `${pos.height}px`
};
Object.assign(effectEl.style, styles);
Object.assign(textEl.style, styles);
textEl.innerText = element.innerText;
}
nav.querySelectorAll('li').forEach(($el) => {
const link = $el.querySelector('a');
const handleClick = (e) => {
updateEffectPosition($el);
if (!$el.classList.contains('active')) {
nav.querySelectorAll('li').forEach(($el) => {
$el.classList.remove('active');
});
effectEl.querySelectorAll('.particle').forEach(($el) => {
effectEl.removeChild($el);
});
$el.classList.add('active');
textEl.classList.remove('active');
setTimeout(() => {
textEl.classList.add('active');
}, 100);
makeParticles(effectEl);
}
};
$el.addEventListener('click', handleClick);
link.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick(e);
}
});
});
const resizeObserver = new ResizeObserver(() => {
const activeEl = nav.querySelector('li.active');
if (activeEl) {
updateEffectPosition(activeEl);
}
});
resizeObserver.observe(document.body);
setTimeout(() => {
nav.querySelector('li').click();
}, 200)
</script>
</body>
</html>
HTML 结构
- nav:定义了一个导航栏,其中包含一个无序列表 ul,列表中有三个导航项(li),分别是 home、about 和 feature,每个导航项中包含一个链接 a。
- effect filter:用于创建一个动态的视觉效果层,可能与背景或过渡效果相关。
- effect text:用于显示导航项的文本内容,并在交互时动态更新。
- script:包含 JavaScript 代码,用于实现页面的交互逻辑和动态效果。
CSS 样式
- backdrop-filter 和 box-shadow 添加了模糊背景和阴影效果,使导航栏看起来更加立体和透明。
- 使用伪元素::before 和::after 添加了额外的视觉效果,如渐变背景和阴影。
- 导航项 li 的样式包括圆角、过渡动画和动态阴影效果,鼠标悬停或点击时会有视觉反馈。
- .effect.filter:通过 filter 和 mix-blend-mode 实现了模糊和混合效果,用于增强视觉效果。
- .effect.text:用于动态显示导航项的文本内容,并通过 transition 实现颜色变化的动画效果。
- .particle 和.point:定义了粒子的样式,包括圆形、透明度变化和动画效果。
- 使用@keyframes 定义了粒子的运动轨迹(particle)和缩放效果(point),粒子会在点击导航项时动态生成并移动。
- @keyframes pill:定义了一个缩放动画,用于动态显示或隐藏效果层。
- --linear-ease:自定义了一个复杂的贝塞尔曲线,用于控制动画的过渡效果。
JavaScript 功能说明
- 监听导航项的点击事件,更新.effect.text 的内容为当前导航项的文本。
- 清除其他导航项的 active 状态,并为当前导航项添加 active 状态。
- 触发粒子效果,动态生成粒子并根据定义的动画效果移动。
- makeParticles 函数:在指定元素内生成粒子,粒子的位置、运动轨迹、颜色等通过随机函数生成。
- createParticle 函数:定义了粒子的属性,包括起始位置、结束位置、运动时间、缩放比例和旋转角度。
- 使用 ResizeObserver 监听窗口大小变化,动态更新效果层的位置和大小。
- 页面加载完成后,自动触发第一个导航项的点击事件,初始化页面效果。
各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!