HTML&CSS&JS:必学!用动态导航栏,让网页颜值飙升 10 倍

这段代码实现了一个具有动态视觉效果和交互功能的导航栏页面,通过 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 监听窗口大小变化,动态更新效果层的位置和大小。
  • 页面加载完成后,自动触发第一个导航项的点击事件,初始化页面效果。

各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!

相关推荐
程序员buddha2 小时前
ThinkPHP8.0+MySQL8.0搭建简单实用电子证书查询系统
javascript·css·mysql·php·layui·jquery·html5
高志小鹏鹏3 小时前
Tailwind CSS都更新到4.0了,你还在抵触吗?
前端·css·postcss
Archie_IT3 小时前
使用DeepSeek+蓝耘快速设计网页简易版《我的世界》小游戏
前端·人工智能·大模型·html·aigc·deepseek
zhaoyong6314 小时前
利用微软的 HTML 应用程序宿主程序的攻击
前端·microsoft·html
Mswanga5 小时前
探秘 CSS 盒子模型:构建网页布局的基石
前端·css
I will.8746 小时前
如何使用 CSS 实现黑色遮罩效果
前端·javascript·css
宇寒风暖7 小时前
HTML 表单详解
前端·笔记·学习·html
前端Hardy7 小时前
HTML&CSS :用 CSS 遮罩,让产品展示图 “高级感爆棚”
css·html
前端Hardy7 小时前
HTML&CSS :1 分钟学会,飞虫变色特效卡片
css·html