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 监听窗口大小变化,动态更新效果层的位置和大小。
  • 页面加载完成后,自动触发第一个导航项的点击事件,初始化页面效果。

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

相关推荐
小*-^-*九21 小时前
php 使用html 生成pdf word wkhtmltopdf 系列2
pdf·html·php
BillKu21 小时前
vue3 样式 css、less、scss、sass 的说明
css·less·sass·scss
乖女子@@@1 天前
css3新增-网格Grid布局
前端·css·css3
Sapphire~1 天前
重学前端013 --- 响应式网页设计 CSS网格布局
前端·css
二十雨辰1 天前
歌词滚动效果
前端·css
hashiqimiya1 天前
html实现右上角有个图标,鼠标移动到该位置出现手型,点击会弹出登录窗口。
前端·html
BillKu1 天前
Vue3 中使用 DOMPurify 对渲染动态 HTML 进行安全净化处理
前端·安全·html
dllmayday1 天前
FontForge 手工扩展 iconfont.ttf
css·qt
BUG创建者2 天前
html获取16个随机颜色并不重复
css·html·css3
DevilSeagull2 天前
JavaScript WebAPI 指南
java·开发语言·javascript·html·ecmascript·html5