动态点阵背景

flyKot2025/10/20

效果预览

cal

分析原理

可以看出是绝对定位了个canvas在背景上,再在canvas上根据元素的宽高画上相应数量的圆点,再给每个圆点设置随机透明度进行闪烁

1.写出主体结构

    <div class="card">
        <div class="cvs_box"><canvas id="card_cvs"></canvas></div>
        <div class="context">cal</div>
    </div>

2.设置css

TIP

内层容器设置mask-image遮罩时,内层容器的边框、shadow等都会被遮挡,所以卡片的边框应写在外层容器

.card {
    position: relative;
    width: 300px;
    height: 300px;
    border-radius: 18px;
    display: flex;
    justify-content: center;
    align-items: center;
    border: 1.5px solid #ededed;
    overflow: hidden;
    box-shadow: 0 0 0 calc(1px + 0) color-mix(in oklab, #000 5%, transparent);
}

.cvs_box {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    mask-image: linear-gradient(#fff, #fff), radial-gradient(ellipse farthest-corner at center, black 0, transparent 90%), linear-gradient(#fff, #fff);
    mask-composite: intersect;
}

.context {
    width: 30px;
    height: 30px;
    background-color: black;
    color: #fff;
    text-align: center;
    z-index: 2;
}

#card_cvs {
    width: 100%;
    height: 100%;
    z-index: 1;
}

3.实现canvas内容

const canvas = document.getElementById("card_cvs")
//保持canvas的width和style.width一致,否则会出现圆点被拉伸情况,height同width
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const ctx = canvas.getContext("2d")
const box_width = canvas.width
const box_height = canvas.height
// 圆点半径
const r = 1.5
const c_width = r
const c_height = r
// 根据卡片宽高计算圆点数量
const c_count_x = Math.round((box_width / c_width) / 2)
const c_count_y = Math.round((box_height / c_height) / 2)
const spacing = 5.5 * r;   // 间距
const color = "#b7b7b7";
// 最小透明度
const minAlpha = 0.2
// 最大透明度
const maxAlpha = 1
// 圆点集合
const circles = [];
for (let i = 0; i * spacing < box_width; i++) {
    for (let j = 0; j * spacing < box_height; j++) {
        circles.push({
            x: i * spacing,
            y: j * spacing,
            radius: r,
            // 每个圆点有自己的初始时间偏移(避免完全同步)
            time: Math.random() * Math.PI * 2 // 随机相位
        });
    }
}

function animate() {
    // 清空整个画布(只清一次!)
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 更新并绘制每个圆点
    circles.forEach(circle => {
        // 每个圆点独立推进自己的时间
        circle.time += 0.04; // 闪烁速度

        // 独立计算透明度
        // Math.sin(circle.time)范围是[-1,1],+1为[0,2]再除2,透明度就在[0,1]
        const alpha = (Math.sin(circle.time) + 1) / 2;
        // max-min得0.8,所以alpha*0.8结果就在[0,0.8]结果再+0.2就得到[0.2,1]
        ctx.globalAlpha = minAlpha + alpha * (maxAlpha - minAlpha);
        ctx.beginPath();
        ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI);
        ctx.fillStyle = color;
        ctx.fill();
    });

    ctx.globalAlpha = 1.0; // 重置透明度
    requestAnimationFrame(animate);
}

animate();