以下是使用 Canvas 实现“点击页面生成渐变圆”的完整代码。点击任意位置会生成一个径向渐变、半径随机的圆,并保留所有已绘制的圆。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>Canvas 幻彩圆点 | 点击生成随机渐变圆</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            user-select: none; /* 避免点击时选中文本 */
        }

        body {
            background: #1a2a32;
            overflow: hidden;
            font-family: 'Segoe UI', 'Poppins', system-ui, -apple-system, sans-serif;
        }

        canvas {
            display: block;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            cursor: crosshair;
            background: #f8f9fc;
            background-image: radial-gradient(circle at 25% 40%, rgba(0,0,0,0.02) 2%, transparent 2.5%),
                              radial-gradient(circle at 75% 85%, rgba(0,0,0,0.015) 1.5%, transparent 2%);
            background-size: 48px 48px, 32px 32px;
            transition: background 0.2s ease;
        }

        /* 控制面板 - 简洁现代 */
        .info-panel {
            position: fixed;
            bottom: 20px;
            left: 20px;
            right: 20px;
            background: rgba(30, 35, 45, 0.72);
            backdrop-filter: blur(12px);
            border-radius: 56px;
            padding: 10px 20px;
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            align-items: center;
            gap: 12px;
            z-index: 100;
            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
            border: 1px solid rgba(255, 255, 255, 0.25);
            font-weight: 500;
            pointer-events: auto;
        }

        .info-text {
            color: white;
            font-size: 0.9rem;
            letter-spacing: 0.5px;
            background: rgba(0,0,0,0.4);
            padding: 6px 14px;
            border-radius: 40px;
            backdrop-filter: blur(4px);
        }

        .info-text span {
            font-weight: bold;
            color: #ffd966;
        }

        button {
            background: rgba(255, 255, 255, 0.2);
            backdrop-filter: blur(4px);
            border: 1px solid rgba(255,255,240,0.6);
            color: white;
            padding: 6px 20px;
            border-radius: 40px;
            font-size: 0.85rem;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s ease;
            font-family: inherit;
        }

        button:hover {
            background: #ff8c42;
            border-color: white;
            transform: scale(0.96);
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
        }

        .counter {
            background: #1e2a36;
            color: #ffd966;
            padding: 5px 14px;
            border-radius: 36px;
            font-size: 0.85rem;
            font-family: monospace;
            font-weight: bold;
        }

        @media (max-width: 560px) {
            .info-panel { padding: 8px 16px; border-radius: 40px; }
            .info-text { font-size: 0.7rem; }
            button { padding: 4px 16px; font-size: 0.75rem; }
        }
    </style>
</head>
<body>

<canvas id="magicCanvas"></canvas>

<div class="info-panel">
    <div class="info-text">
        ✨ 点击任意位置 · <span>径向渐变</span> + <span>随机半径</span>
    </div>
    <div class="counter" id="circleCounter">⚪ 圆点数量: 0</div>
    <button id="clearBtn">🧹 清空画布</button>
</div>

<script>
    (function() {
        // ----- DOM 元素 -----
        const canvas = document.getElementById('magicCanvas');
        const ctx = canvas.getContext('2d');
        const circleCounterSpan = document.getElementById('circleCounter');
        const clearBtn = document.getElementById('clearBtn');

        // ----- 存储所有圆的数据 -----
        let circles = [];      // 每个元素: { x, y, radius, outerColor, innerColor }

        // ----- 随机工具函数 -----
        // 随机整数 [min, max]
        function randomInt(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        // 随机半径 (20px ~ 130px,范围适中,视觉丰富)
        function getRandomRadius() {
            return randomInt(22, 128);
        }

        // 随机生成鲜艳的颜色 (HSL 模式 饱和度65% 亮度65%)
        // 返回 hsl 字符串,也可以作为径向渐变外圈主色
        function getRandomVividColor() {
            const hue = Math.floor(Math.random() * 360);
            // 饱和度 60% ~ 85% 鲜艳但柔和
            const saturation = randomInt(60, 88);
            // 亮度 55% ~ 75% 明亮不刺眼
            const lightness = randomInt(55, 78);
            return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
        }

        // 生成内圈颜色 (亮色/暖白带微透明感,让渐变更通透)
        function getInnerGlowColor() {
            // 柔白 / 淡金色 / 极浅粉,增加层次
            const variants = [
                'rgba(255, 255, 245, 0.95)',
                'rgba(255, 250, 225, 0.92)',
                'rgba(255, 248, 225, 0.96)',
                'rgba(250, 245, 235, 0.96)',
                '#fff9e8'
            ];
            return variants[Math.floor(Math.random() * variants.length)];
        }

        // 根据圆的参数创建一个径向渐变对象 (基于当前context)
        function createRadialGradientForCircle(ctx, cx, cy, radius, innerColor, outerColor) {
            // 径向渐变: 从圆心 (cx,cy) 半径 0 开始,到半径 radius 结束
            const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
            gradient.addColorStop(0, innerColor);
            gradient.addColorStop(0.45, innerColor);      // 内芯保持通透亮色
            gradient.addColorStop(0.78, outerColor);
            gradient.addColorStop(1, outerColor);
            return gradient;
        }

        // ----- 绘制所有圆 (保留历史,不丢失) -----
        function drawAllCircles() {
            if (!ctx) return;
            
            // 获取当前canvas的实际像素尺寸
            const w = canvas.width;
            const h = canvas.height;
            
            // 清空画布并填充优雅的背景 (浅色网格质感)
            ctx.clearRect(0, 0, w, h);
            
            // 底色: 温柔米灰 + 微妙的噪点纹理感(通过径向渐变底纹)
            const gradBack = ctx.createLinearGradient(0, 0, w, h);
            gradBack.addColorStop(0, '#fefaf5');
            gradBack.addColorStop(1, '#f0f2f5');
            ctx.fillStyle = gradBack;
            ctx.fillRect(0, 0, w, h);
            
            // 极浅网格线装饰 (增加精致感)
            ctx.save();
            ctx.globalAlpha = 0.2;
            ctx.beginPath();
            ctx.strokeStyle = '#cbd5e1';
            ctx.lineWidth = 0.5;
            for (let i = 0; i < w; i += 40) {
                ctx.beginPath();
                ctx.moveTo(i, 0);
                ctx.lineTo(i, h);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(0, i);
                ctx.lineTo(w, i);
                ctx.stroke();
            }
            ctx.restore();
            
            // 绘制所有存储的圆 (从旧到新,后绘制的在上层视觉无影响)
            for (let i = 0; i < circles.length; i++) {
                const circle = circles[i];
                const { x, y, radius, outerColor, innerColor } = circle;
                
                // 创建该圆的径向渐变
                const gradient = createRadialGradientForCircle(ctx, x, y, radius, innerColor, outerColor);
                
                ctx.beginPath();
                ctx.arc(x, y, radius, 0, Math.PI * 2);
                ctx.fillStyle = gradient;
                ctx.fill();
                
                // 增加极细高光边缘 (让圆更立体,轻微白边)
                ctx.save();
                ctx.globalCompositeOperation = 'lighter';
                ctx.beginPath();
                ctx.arc(x, y, radius - 1, 0, Math.PI * 2);
                ctx.fillStyle = 'rgba(255, 255, 240, 0.18)';
                ctx.fill();
                ctx.restore();
                
                // 小光晕效果 (增强圆质感)
                ctx.save();
                ctx.shadowBlur = 0; // 重置阴影避免叠加过多性能
                ctx.beginPath();
                ctx.arc(x, y, radius * 0.2, 0, Math.PI * 2);
                ctx.fillStyle = 'rgba(255, 255, 245, 0.4)';
                ctx.fill();
                ctx.restore();
            }
            
            // 更新显示圆的数量
            circleCounterSpan.innerHTML = `⚪ 圆点数量: ${circles.length}`;
        }
        
        // ----- 添加新的圆 (点击位置) -----
        function addCircleAtMouse(clientX, clientY) {
            // 获取canvas相对浏览器视口的实际尺寸和偏移量
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;   // canvas物理像素与CSS比例
            const scaleY = canvas.height / rect.height;
            
            // 计算 canvas 画布上的实际坐标 (物理像素坐标)
            let canvasX = (clientX - rect.left) * scaleX;
            let canvasY = (clientY - rect.top) * scaleY;
            
            // 边界修剪(防止超出画布导致渐变异常,但不影响绘制,但限制坐标在画布内更好)
            canvasX = Math.min(Math.max(0, canvasX), canvas.width);
            canvasY = Math.min(Math.max(0, canvasY), canvas.height);
            
            // 随机半径
            const radius = getRandomRadius();
            
            // 随机外圈颜色(鲜艳,每次不同)
            const outerColor = getRandomVividColor();
            // 内圈柔和亮色
            const innerColor = getInnerGlowColor();
            
            // 存储圆的信息
            circles.push({
                x: canvasX,
                y: canvasY,
                radius: radius,
                outerColor: outerColor,
                innerColor: innerColor
            });
            
            // 重绘所有圆(包括新圆)
            drawAllCircles();
        }
        
        // ----- 清空所有圆 -----
        function clearAllCircles() {
            circles = [];
            drawAllCircles();
        }
        
        // ----- 自适应 canvas 尺寸 (保持高清,防止模糊) -----
        function resizeCanvas() {
            const w = window.innerWidth;
            const h = window.innerHeight;
            
            // 设置 canvas 实际像素尺寸 (避免CSS拉伸模糊)
            canvas.width = w;
            canvas.height = h;
            
            // 重置画布样式尺寸
            canvas.style.width = `${w}px`;
            canvas.style.height = `${h}px`;
            
            // 调整尺寸后必须重绘所有圆 (否则会丢失绘图)
            drawAllCircles();
        }
        
        // ----- 窗口大小改变时,可选策略:保持原有圆的位置相对于新画布坐标不变还是清空?
        // 为了更自然: 保留已绘制的圆,但因为画布大小改变,旧圆的坐标依然基于左上角但视觉上会偏移,
        // 不过保留历史圆可以避免用户意外丢失作品,也更有趣。同时由于背景网格适应,坐标在逻辑上没变,
        // 只是窗口变化后圆相对窗口位置变化,但用户在缩放后依然能看到之前作品,属于预期行为。不强制清空。
        // 但为了完美展示,不清空,保留所有圆并重绘。(平滑过渡)
        function handleResize() {
            resizeCanvas();
            // resizeCanvas 内部已经 drawAllCircles, 因为尺寸重置后,原来的坐标是基于绝对坐标,
            // 画面仅仅是在新的canvas尺寸下重新绘制,位置相对于左上角不变。 完美保留。
            // (如果希望清空可以调用clearAllCircles,但为了用户友好,保留)
        }
        
        // ----- 点击canvas事件 (生成圆) -----
        function onCanvasClick(e) {
            let clientX, clientY;
            if (e.touches) {
                // 移动端触摸支持
                clientX = e.touches[0].clientX;
                clientY = e.touches[0].clientY;
                e.preventDefault();
            } else {
                clientX = e.clientX;
                clientY = e.clientY;
            }
            addCircleAtMouse(clientX, clientY);
        }
        
        // ----- 移动端支持触摸生成圆 -----
        function onCanvasTouch(e) {
            e.preventDefault();
            if (e.touches.length) {
                const touch = e.touches[0];
                addCircleAtMouse(touch.clientX, touch.clientY);
            }
        }
        
        // ----- 初始化事件与尺寸 -----
        function init() {
            resizeCanvas();
            
            // 监听鼠标点击
            canvas.addEventListener('click', onCanvasClick);
            // 触摸屏支持
            canvas.addEventListener('touchstart', onCanvasTouch, { passive: false });
            // 窗口大小改变
            window.addEventListener('resize', () => {
                handleResize();
            });
            
            // 清空按钮事件
            clearBtn.addEventListener('click', (e) => {
                e.stopPropagation();
                clearAllCircles();
            });
            
            // 初始演示预置两个示例圆(吸引用户,展示效果但不干扰体验,可选)
            // 为了让初始画布不单调,主动添加2个柔和展示圆(可注释,但添加后更直观)
            setTimeout(() => {
                if (circles.length === 0) {
                    // 示例圆1: 画布中央偏左
                    const demoRadius1 = 64;
                    const demoX1 = canvas.width * 0.3;
                    const demoY1 = canvas.height * 0.5;
                    circles.push({
                        x: demoX1,
                        y: demoY1,
                        radius: demoRadius1,
                        outerColor: '#f97316',   // 暖橙
                        innerColor: '#fff2dd'
                    });
                    // 示例圆2: 右侧区域
                    const demoRadius2 = 78;
                    const demoX2 = canvas.width * 0.7;
                    const demoY2 = canvas.height * 0.65;
                    circles.push({
                        x: demoX2,
                        y: demoY2,
                        radius: demoRadius2,
                        outerColor: '#3b82f6',   // 碧蓝
                        innerColor: '#eef5ff'
                    });
                    drawAllCircles();
                }
            }, 100);
        }
        
        // 启动
        init();
    })();
</script>
</body>
</html>
```

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐