用 CSS 打造浪漫流星雨效果,代码里的诗意星空

408 阅读9分钟

最近突发奇想,想用代码实现一个流星雨的效果,毕竟谁能拒绝一场浪漫的视觉盛宴呢?在一番捣鼓后,终于用 CSS 实现了这个效果,现在就来和大家分享一下实现过程。

流星雨.gif

整体布局与背景设置

先看整体布局,HTML 结构很简单,就几个div元素,分别用于呈现星空背景、山脉轮廓和流星容器。

<div class="stars"></div>
<div class="mountains"></div>

<div class="meteor-container">
    <!-- 基础流星 -->
    <div class="meteor"></div>
    <div class="meteor small dim"></div>
    <div class="meteor large bright"></div>
    <div class="meteor"></div>
    <div class="meteor bright"></div>
    <div class="meteor small"></div>
    <div class="meteor large"></div>
    <div class="meteor dim"></div>
    <div class="meteor"></div>
    <div class="meteor bright"></div>
    <div class="meteor small dim"></div>
    <div class="meteor"></div>
    <div class="meteor large bright"></div>
    <div class="meteor"></div>
    <div class="meteor"></div>
</div>

核心部分还是 CSS 样式的编写。首先是背景,用linear-gradient创造出渐变效果,模拟深邃夜空。从深蓝到更深的蓝紫过渡,为整个场景奠定基调。

body {
    background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #1a1a2e 100%);
    overflow: hidden;
    height: 100vh;
    position: relative;
}

再添加山脉轮廓,通过clip-path属性定义多边形形状,勾勒出起伏山脉,还用linear-gradient实现从深到浅的颜色过渡,模拟山脉的光影效果。

.mountains {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 30%;
    background: linear-gradient(to top, #0a0a0a 0%, #1a1a1a 50%, transparent 100%);
    clip-path: polygon(
        0% 100%, 
        0% 70%, 
        10% 60%, 
        20% 65%, 
        30% 55%, 
        40% 60%, 
        50% 50%, 
        60% 55%, 
        70% 45%, 
        80% 50%, 
        90% 40%, 
        100% 45%, 
        100% 100%
    );
}

营造星空闪烁效果

星空闪烁是流星雨场景的重要部分,用background-image配合radial-gradient来模拟星星。一个个径向渐变代表星星,设置不同的位置、透明度和大小,营造远近不同的星星效果。

.stars {
    position: absolute;
    width: 100%;
    height: 100%;
    background-image: 
        radial-gradient(1px 1px at 20px 30px, rgba(255,255,255,0.8), transparent),
        radial-gradient(1px 1px at 40px 70px, rgba(255,255,255,0.6), transparent),
        radial-gradient(0.5px 0.5px at 90px 40px, rgba(255,255,255,0.9), transparent),
        radial-gradient(1px 1px at 130px 80px, rgba(255,255,255,0.5), transparent),
        radial-gradient(0.5px 0.5px at 160px 30px, rgba(255,255,255,0.7), transparent);
    background-repeat: repeat;
    background-size: 200px 150px;
    animation: twinkle 3s ease-in-out infinite alternate;
}

这里还定义了一个twinkle动画,让星星闪烁起来。通过改变透明度,在0%100%关键帧之间切换,模拟星星闪烁的视觉效果。

@keyframes twinkle {
    0% { opacity: 0.7; }
    100% { opacity: 1; }
}

打造流星特效

流星是这个效果的主角,先来设置流星容器,用position: absolute让流星在整个页面范围内运动。

.meteor-container {
    position: absolute;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

每个流星都是一个div元素,设置为圆形,用border-radius: 50%实现。流星默认是透明的,通过动画让它出现、划过并消失。

.meteor {
    position: absolute;
    width: 2px;
    height: 2px;
    background: white;
    border-radius: 50%;
    opacity: 0;
}

流星的尾巴是用伪元素::before实现的。通过linear-gradient创建从亮到暗的渐变效果,模拟流星划过留下的轨迹。再通过transform旋转和平移,调整尾巴的角度和位置。

.meteor::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100px;
    height: 1px;
    background: linear-gradient(90deg, 
        rgba(255, 255, 255, 1) 0%,
        rgba(255, 255, 255, 0.8) 20%,
        rgba(255, 255, 255, 0.6) 40%,
        rgba(255, 255, 255, 0.3) 70%,
        transparent 100%);
    transform: rotate(-135deg);
    transform-origin: 0 0;
}

最关键的是流星的动画效果,定义meteor-fall动画。在0%关键帧,流星在页面外且透明;在10%关键帧,流星出现;在90%关键帧,流星保持可见;在100%关键帧,流星消失在页面外。通过transform改变流星的位置,实现从页面一端划过到另一端的效果。

@keyframes meteor-fall {
    0% {
        opacity: 0;
        transform: translateX(-200px) translateY(-200px);
    }
    10% {
        opacity: 1;
    }
    90% {
        opacity: 1;
    }
    100% {
        opacity: 0;
        transform: translateX(calc(100vw + 300px)) translateY(calc(100vh + 300px));
    }
}

为了让流星雨效果更丰富,给不同的流星设置了不同的起始位置、动画时长和延迟时间。通过nth-child选择器,给每个流星添加独特的样式。比如nth-child(1)设置在left: 5%; top: 5%;的位置,动画时长3s,延迟0s

.meteor:nth-child(1) {
    left: 5%;
    top: 5%;
    animation: meteor - fall 3s linear infinite;
    animation - delay: 0s;
}

还有不同大小和亮度的流星,大流星增加widthheight,并在伪元素中增加widthheight,让尾巴更明显。小流星则减小相应尺寸。明亮流星通过box - shadow添加光晕效果,暗淡流星降低opacity并调整伪元素渐变颜色,让流星看起来更真实多样。

/* 大流星样式 */
.meteor.large {
    width: 3px;
    height: 3px;
}
.meteor.large::before {
    width: 150px;
    height: 2px;
}
/* 小流星样式 */
.meteor.small {
    width: 1px;
    height: 1px;
}
.meteor.small::before {
    width: 60px;
    height: 0.5px;
}
/* 明亮流星样式 */
.meteor.bright {
    box-shadow: 0 0 6px rgba(255, 255, 255, 0.8);
}
.meteor.bright::before {
    background: linear-gradient(90deg, 
        rgba(255, 255, 255, 1) 0%,
        rgba(255, 255, 255, 0.9) 20%,
        rgba(255, 255, 255, 0.7) 40%,
        rgba(255, 255, 255, 0.4) 70%,
        transparent 100%);
}
/* 暗淡流星样式 */
.meteor.dim {
    opacity: 0.6;
}
.meteor.dim::before {
    background: linear-gradient(90deg, 
        rgba(255, 255, 255, 0.7) 0%,
        rgba(255, 255, 255, 0.5) 20%,
        rgba(255, 255, 255, 0.3) 40%,
        rgba(255, 255, 255, 0.1) 70%,
        transparent 100%);
}

JavaScript 交互增强

为了让用户能参与其中,用 JavaScript 添加了一些交互效果。点击页面可以添加流星,通过document.addEventListener('click', () => {})监听点击事件,每次点击创建 3 个流星,通过setTimeout控制流星出现的间隔。

document.addEventListener('click', () => {
    for (let i = 0; i < 3; i++) {
        setTimeout(() => createMeteor(), i * 200);
    }
});

还设置了定时器,定期自动添加流星。通过setInterval(() => {}, 3000)每 3 秒检查一次,如果流星数量小于 20,就调用createMeteor函数创建新流星。

setInterval(() => {
    if (meteorCount < 20) {
        createMeteor();
    }
}, 3000);

createMeteor函数负责创建流星元素,并给流星随机添加样式、位置、动画时长和延迟时间。随机选择预定义的样式类,如smalllargebrightdim等,随机设置lefttop位置,随机生成动画时长和延迟时间,让每次生成的流星都不一样。

function createMeteor() {
    const container = document.querySelector('.meteor - container');
    const meteor = document.createElement('div');
    meteor.className ='meteor';
    const styles = ['', 'small', 'large', 'bright', 'dim','small dim', 'large bright'];
    const randomStyle = styles[Math.floor(Math.random() * styles.length)];
    meteor.className ='meteor'+ randomStyle;
    meteor.style.left = Math.random() * 80 + '%';
    meteor.style.top = Math.random() * 40 + '%';
    meteor.style.animationDuration = (2 + Math.random() * 2)+'s';
    meteor.style.animationDelay = Math.random() * 2 +'s';
    container.appendChild(meteor);
    meteorCount++;
    setTimeout(() => {
        if (meteor.parentNode) {
            meteor.parentNode.removeChild(meteor);
            meteorCount--;
        }
    }, 7000);
}

通过这些 CSS 和 JavaScript 代码,就打造出了一个浪漫的流星雨效果。在这个过程中,充分发挥了 CSS 的样式能力和 JavaScript 的交互能力,将原本静态的页面变得生动有趣。希望大家也能从中找到乐趣,尝试自己动手实现更多有趣的效果。

完整CSS代码如下,大家可以直接复制到 HTML 文件中运行,感受这场代码里的流星雨。

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #1a1a2e 100%);
    overflow: hidden;
    height: 100vh;
    position: relative;
}

/* 山脉轮廓 */
.mountains {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 30%;
    background: linear-gradient(to top, #0a0a0a 0%, #1a1a1a 50%, transparent 100%);
    clip-path: polygon(
        0% 100%, 
        0% 70%, 
        10% 60%, 
        20% 65%, 
        30% 55%, 
        40% 60%, 
        50% 50%, 
        60% 55%, 
        70% 45%, 
        80% 50%, 
        90% 40%, 
        100% 45%, 
        100% 100%
    );
}

/* 星空背景 */
.stars {
    position: absolute;
    width: 100%;
    height: 100%;
    background-image: 
        radial-gradient(1px 1px at 20px 30px, rgba(255,255,255,0.8), transparent),
        radial-gradient(1px 1px at 40px 70px, rgba(255,255,255,0.6), transparent),
        radial-gradient(0.5px 0.5px at 90px 40px, rgba(255,255,255,0.9), transparent),
        radial-gradient(1px 1px at 130px 80px, rgba(255,255,255,0.5), transparent),
        radial-gradient(0.5px 0.5px at 160px 30px, rgba(255,255,255,0.7), transparent);
    background-repeat: repeat;
    background-size: 200px 150px;
    animation: twinkle 3s ease-in-out infinite alternate;
}

@keyframes twinkle {
    0% { opacity: 0.7; }
    100% { opacity: 1; }
}

/* 流星容器 */
.meteor-container {
    position: absolute;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

/* 流星基础样式 */
.meteor {
    position: absolute;
    width: 2px;
    height: 2px;
    background: white;
    border-radius: 50%;
    opacity: 0;
}

/* 流星尾光 */
.meteor::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100px;
    height: 1px;
    background: linear-gradient(90deg, 
        rgba(255, 255, 255, 1) 0%,
        rgba(255, 255, 255, 0.8) 20%,
        rgba(255, 255, 255, 0.6) 40%,
        rgba(255, 255, 255, 0.3) 70%,
        transparent 100%);
    transform: rotate(-135deg);
    transform-origin: 0 0;
}

/* 流星动画 */
@keyframes meteor-fall {
    0% {
        opacity: 0;
        transform: translateX(-200px) translateY(-200px);
    }
    10% {
        opacity: 1;
    }
    90% {
        opacity: 1;
    }
    100% {
        opacity: 0;
        transform: translateX(calc(100vw + 200px)) translateY(calc(100vh + 200px));
    }
}

/* 不同的流星样式 */
.meteor:nth-child(1) {
    left: 10%;
    top: 5%;
    animation: meteor-fall 3s linear infinite;
    animation-delay: 0s;
}

.meteor:nth-child(2) {
    left: 20%;
    top: 8%;
    animation: meteor-fall 2.5s linear infinite;
    animation-delay: 0.5s;
}

.meteor:nth-child(3) {
    left: 15%;
    top: 12%;
    animation: meteor-fall 3.2s linear infinite;
    animation-delay: 1s;
}

.meteor:nth-child(4) {
    left: 30%;
    top: 6%;
    animation: meteor-fall 2.8s linear infinite;
    animation-delay: 1.5s;
}

.meteor:nth-child(5) {
    left: 25%;
    top: 15%;
    animation: meteor-fall 3.5s linear infinite;
    animation-delay: 2s;
}

.meteor:nth-child(6) {
    left: 40%;
    top: 10%;
    animation: meteor-fall 2.7s linear infinite;
    animation-delay: 2.5s;
}

.meteor:nth-child(7) {
    left: 35%;
    top: 18%;
    animation: meteor-fall 3.1s linear infinite;
    animation-delay: 3s;
}

.meteor:nth-child(8) {
    left: 50%;
    top: 8%;
    animation: meteor-fall 2.9s linear infinite;
    animation-delay: 3.5s;
}

.meteor:nth-child(9) {
    left: 45%;
    top: 20%;
    animation: meteor-fall 3.3s linear infinite;
    animation-delay: 4s;
}

.meteor:nth-child(10) {
    left: 60%;
    top: 12%;
    animation: meteor-fall 2.6s linear infinite;
    animation-delay: 4.5s;
}

.meteor:nth-child(11) {
    left: 55%;
    top: 25%;
    animation: meteor-fall 3.4s linear infinite;
    animation-delay: 5s;
}

.meteor:nth-child(12) {
    left: 70%;
    top: 15%;
    animation: meteor-fall 2.4s linear infinite;
    animation-delay: 5.5s;
}

.meteor:nth-child(13) {
    left: 65%;
    top: 30%;
    animation: meteor-fall 3.6s linear infinite;
    animation-delay: 6s;
}

.meteor:nth-child(14) {
    left: 80%;
    top: 18%;
    animation: meteor-fall 2.8s linear infinite;
    animation-delay: 6.5s;
}

.meteor:nth-child(15) {
    left: 75%;
    top: 35%;
    animation: meteor-fall 3.2s linear infinite;
    animation-delay: 7s;
}

/* 大流星样式 */
.meteor.large {
    width: 3px;
    height: 3px;
}

.meteor.large::before {
    width: 150px;
    height: 2px;
}

/* 小流星样式 */
.meteor.small {
    width: 1px;
    height: 1px;
}

.meteor.small::before {
    width: 60px;
    height: 0.5px;
}

/* 明亮流星样式 */
.meteor.bright {
    box-shadow: 0 0 6px rgba(255, 255, 255, 0.8);
}

.meteor.bright::before {
    background: linear-gradient(90deg, 
        rgba(255, 255, 255, 1) 0%,
        rgba(255, 255, 255, 0.9) 20%,
        rgba(255, 255, 255, 0.7) 40%,
        rgba(255, 255, 255, 0.4) 70%,
        transparent 100%);
}

/* 暗淡流星样式 */
.meteor.dim {
    opacity: 0.6;
}

.meteor.dim::before {
    background: linear-gradient(90deg, 
        rgba(255, 255, 255, 0.7) 0%,
        rgba(255, 255, 255, 0.5) 20%,
        rgba(255, 255, 255, 0.3) 40%,
        rgba(255, 255, 255, 0.1) 70%,
        transparent 100%);
}

/* 随机样式应用 */
.meteor:nth-child(odd) {
    animation-duration: 2.5s;
}

.meteor:nth-child(even) {
    animation-duration: 3.5s;
}

.meteor:nth-child(3n) {
    animation-duration: 2.8s;
}

.meteor:nth-child(4n) {
    animation-duration: 3.2s;
}

.meteor:nth-child(5n) {
    animation-duration: 2.7s;
}

/* 响应式设计 */
@media (max-width: 768px) {
    .meteor::before {
        width: 60px;
    }

    .meteor.large::before {
        width: 100px;
    }

    .meteor.small::before {
        width: 40px;
    }
}