Canvas教学:点击画布,随机产生各种形状-由Deepseek产生
【代码】Canvas基础:点击画布,随机产生各种形状-由Deepseek产生。
·
<!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>随机形状画布 | 点击绘制圆/矩形/三角形</title>
<style>
* {
user-select: none;
box-sizing: border-box;
}
body {
background: linear-gradient(145deg, #e0eafc 0%, #cfdef3 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
margin: 0;
padding: 20px;
}
.card {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(2px);
border-radius: 2rem;
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.05);
padding: 1.5rem;
transition: all 0.2s ease;
}
.canvas-container {
background: #fef9e8;
border-radius: 1.5rem;
box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.05), 0 10px 20px rgba(0, 0, 0, 0.1);
padding: 8px;
}
canvas {
display: block;
margin: 0 auto;
border-radius: 1rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
cursor: crosshair;
background-color: #fffef7;
transition: transform 0.1s ease;
width: 100%;
height: auto;
}
canvas:active {
transform: scale(0.99);
}
.info-panel {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 1.2rem;
gap: 1rem;
flex-wrap: wrap;
}
.badge {
background: #2c3e66;
color: white;
padding: 0.5rem 1rem;
border-radius: 2rem;
font-size: 0.85rem;
font-weight: 500;
letter-spacing: 0.3px;
backdrop-filter: blur(4px);
background: rgba(44, 62, 102, 0.9);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.badge i {
font-style: normal;
display: inline-block;
margin-right: 6px;
}
button {
background: #ff8c5a;
border: none;
color: white;
font-weight: bold;
padding: 0.6rem 1.4rem;
border-radius: 3rem;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
display: inline-flex;
align-items: center;
gap: 8px;
font-family: inherit;
}
button:hover {
background: #ff6e3a;
transform: translateY(-2px);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.15);
}
button:active {
transform: translateY(1px);
}
.counter {
background: #3c2e2a;
background: #1e2a3e;
padding: 0.4rem 1rem;
border-radius: 2rem;
font-size: 0.85rem;
font-weight: 500;
color: #eef4ff;
}
footer {
margin-top: 1rem;
text-align: center;
font-size: 0.7rem;
color: #3b4b6e;
opacity: 0.8;
}
@media (max-width: 650px) {
.card {
padding: 1rem;
}
.badge, .counter {
font-size: 0.7rem;
padding: 0.3rem 0.8rem;
}
button {
padding: 0.4rem 1rem;
font-size: 0.8rem;
}
}
</style>
</head>
<body>
<div>
<div class="card">
<div class="canvas-container">
<canvas id="shapeCanvas" width="800" height="500" style="width:100%; height:auto; max-width:800px; aspect-ratio:800/500"></canvas>
</div>
<div class="info-panel">
<div class="badge">
🎲 随机形状 | 圆 ● 矩形 ■ 三角形 ▲
</div>
<div class="counter" id="shapeCounter">
✨ 已绘制: 0
</div>
<button id="clearBtn">
🧽 清除画布
</button>
</div>
<footer>
💡 点击画布任意位置 → 随机绘制圆形 / 矩形 / 三角形 <br>
🎨 每次位置、大小、颜色完全不同!
</footer>
</div>
</div>
<script>
(function() {
// ----- 获取canvas元素与上下文 -----
const canvas = document.getElementById('shapeCanvas');
const ctx = canvas.getContext('2d');
// 固定尺寸 (与canvas属性一致)
const W = 800;
const H = 500;
// 确保canvas属性尺寸与变量同步 (预防css缩放影响坐标)
canvas.width = W;
canvas.height = H;
// 计数器:累计绘制的形状总数
let shapeCount = 0;
const counterSpan = document.getElementById('shapeCounter');
// ----- 辅助函数:随机整数 [min, max] 包含两端 -----
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// ----- 随机浮点数 [min, max) 主要用于颜色透明通道可选,但颜色直接用整数rgba更稳定 -----
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
// ----- 生成鲜艳随机颜色 (rgba 半透明, 0.6~0.95 透明度,叠加好看)-----
function randomColor() {
// 高饱和度,明亮色调,带一定透明度展示叠加效果
const r = randomInt(50, 255);
const g = randomInt(40, 240);
const b = randomInt(60, 255);
// 透明度 0.65 ~ 0.92,既能看清重叠层次,又鲜艳
const alpha = randomFloat(0.65, 0.92);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
// ----- 随机绘制圆形(完全在画布内)-----
function drawRandomCircle() {
// 半径范围 15~70 (70最大不超过边界, 画布高500宽800, 半径70保证圆心范围足够)
const minR = 15;
const maxR = 68;
const radius = randomInt(minR, maxR);
// 圆心坐标必须满足: radius <= x <= W - radius, radius <= y <= H - radius
const minX = radius;
const maxX = W - radius;
const minY = radius;
const maxY = H - radius;
// 边界安全判定 (如果画布太小或半径极端,但W=800 H=500,半径最大68,范围充裕)
if (minX > maxX || minY > maxY) {
// 极少数极端情况,降级半径 (但不会发生,防御)
return drawRandomCircle();
}
const centerX = randomInt(minX, maxX);
const centerY = randomInt(minY, maxY);
const fillColor = randomColor();
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.fillStyle = fillColor;
ctx.fill();
// 增加轻微边框让形状更立体
ctx.strokeStyle = "rgba(0,0,0,0.2)";
ctx.lineWidth = 1.2;
ctx.stroke();
}
// ----- 随机绘制矩形(完全在画布内)-----
function drawRandomRect() {
// 矩形宽高范围 25~120 确保不超出画布 (宽度范围25~110,高度范围25~100)
const minW = 28;
const maxW = 110;
const minH = 28;
const maxH = 100;
const rectW = randomInt(minW, maxW);
const rectH = randomInt(minH, maxH);
// 左上角坐标范围: x ∈ [0, W - rectW] , y ∈ [0, H - rectH]
const maxX = W - rectW;
const maxY = H - rectH;
if (maxX < 0 || maxY < 0) {
// 万一超界(极端尺寸),降级重试(几乎不会)
return drawRandomRect();
}
const x = randomInt(0, maxX);
const y = randomInt(0, maxY);
const fillColor = randomColor();
ctx.fillStyle = fillColor;
ctx.fillRect(x, y, rectW, rectH);
ctx.strokeStyle = "rgba(0,0,0,0.2)";
ctx.lineWidth = 1.2;
ctx.strokeRect(x, y, rectW, rectH);
}
// ----- 检测三点是否共线(面积接近于0)-----
function isCollinear(x1, y1, x2, y2, x3, y3) {
// 三角形面积公式 *2 的绝对值,若面积 < 1e-6 视为共线或退化
const area2 = Math.abs((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1));
return area2 < 1e-6;
}
// ----- 生成随机三角形(三个顶点完全在画布内,且非退化)-----
function drawRandomTriangle() {
// 最多尝试次数,防止无限循环 (实际随机概率极低)
let maxAttempts = 35;
let x1, y1, x2, y2, x3, y3;
let valid = false;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
// 三个点完全在 [0, W] 和 [0, H] 范围内
x1 = randomInt(5, W - 5);
y1 = randomInt(5, H - 5);
x2 = randomInt(5, W - 5);
y2 = randomInt(5, H - 5);
x3 = randomInt(5, W - 5);
y3 = randomInt(5, H - 5);
if (!isCollinear(x1, y1, x2, y2, x3, y3)) {
valid = true;
break;
}
}
// 保底方案:如果依然共线,手动微调第二个点
if (!valid) {
// 制造一个绝对有效的钝角三角形
x1 = 60; y1 = 70;
x2 = 400; y2 = 80;
x3 = 250; y3 = 420;
// 确保都在边界内(上面值均在范围内)
}
const fillColor = randomColor();
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.closePath();
ctx.fillStyle = fillColor;
ctx.fill();
ctx.strokeStyle = "rgba(0,0,0,0.2)";
ctx.lineWidth = 1.2;
ctx.stroke();
}
// ----- 随机选择形状类型 0:圆, 1:矩形, 2:三角形 -----
function drawRandomShape() {
const shapeType = randomInt(0, 2); // 0,1,2 三种均匀随机
switch(shapeType) {
case 0:
drawRandomCircle();
break;
case 1:
drawRandomRect();
break;
case 2:
drawRandomTriangle();
break;
default:
drawRandomCircle();
}
// 增加计数并更新UI
shapeCount++;
counterSpan.innerHTML = `✨ 已绘制: ${shapeCount}`;
}
// ----- 清除整个画布(重置为纯白背景)-----
function clearCanvas() {
ctx.clearRect(0, 0, W, H);
// 设置清爽的米白底纹,增加一点网格感让画布更好看(可选,不影响随机形状展示)
ctx.fillStyle = "#fffef7";
ctx.fillRect(0, 0, W, H);
// 绘制非常淡的辅助参考线(极浅灰色,不干扰视觉,仅增加精致感)
ctx.save();
ctx.globalAlpha = 0.15;
ctx.beginPath();
ctx.strokeStyle = "#aaa";
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 % H);
ctx.lineTo(W, i % H);
ctx.stroke();
}
ctx.restore();
// 重置计数器为0
shapeCount = 0;
counterSpan.innerHTML = `✨ 已绘制: 0`;
}
// ----- 初始化画布 (首次加载白色+浅浅网格,视觉舒适) -----
function initCanvasBackground() {
ctx.fillStyle = "#fffef7";
ctx.fillRect(0, 0, W, H);
ctx.save();
ctx.globalAlpha = 0.12;
ctx.beginPath();
ctx.strokeStyle = "#8b9bb0";
ctx.lineWidth = 0.6;
for (let i = 0; i < W; i += 45) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, H);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, i % H);
ctx.lineTo(W, i % H);
ctx.stroke();
}
ctx.restore();
}
// ----- canvas 点击事件: 每次点击随机绘制一个新图形(不清除之前)-----
function onCanvasClick(e) {
// 获取点击坐标(虽然本需求与点击位置无关,但为了防止误触发事件,只需执行绘制)
// 完全随机位置,不依赖鼠标坐标,更自由
drawRandomShape();
}
// ----- 设置清除按钮事件 -----
function setupClearButton() {
const clearBtn = document.getElementById('clearBtn');
if (clearBtn) {
clearBtn.addEventListener('click', (e) => {
e.stopPropagation();
clearCanvas();
});
}
}
// ----- 增加一个小彩蛋: 键盘按 C 也可清除,方便演示,但不影响主要功能(可选项)-----
function setupKeyboardShortcut() {
window.addEventListener('keydown', (e) => {
if (e.key === 'c' || e.key === 'C') {
// 避免和浏览器保存快捷键冲突
if (e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
e.preventDefault();
clearCanvas();
}
}
});
}
// ----- 确保所有形状在边界安全,另外三角形也完全在canvas内 (三点范围保证)-----
// 额外添加一个友好的提示:对于矩形和圆,边界函数已经保证完全可见;三角形三点生成均在画布内部(5~W-5边界留微余量)
// 所有随机生成都完美符合要求
// 为了增加趣味性,可随机附加微小阴影?但不影响规范,不加阴影保持清爽。
// 但是为了视觉柔和,在绘制时全局设置一点点阴影会导致所有形状重叠阴影变脏,不开启。
// ----- 初始化事件和背景,并展示示例欢迎形状(可选但为了展示首次状态,可以绘制一个初始形状)-----
function init() {
initCanvasBackground();
setupClearButton();
canvas.addEventListener('click', onCanvasClick);
setupKeyboardShortcut();
// 为了让第一次打开画布不空旷,绘制一个随机的初始形状(也可以不绘制,但符合用户预期展示功能)
// 但题目没有强制要求,但给一个示例形状比较友好,展示效果,同时计数+1
// 随机绘制一个形状展示“点击即可开始”
drawRandomShape(); // 首屏自带一个随机形状,表明功能正常且计数从1开始
}
// 启动一切
init();
// 额外说明: 由于每次绘制都是随机位置和大小,圆形半径每次不同;矩形宽高不同;三角形三点完全随机导致边长/面积变化极大
// 满足【每次位置和大小都不一样】,并且形状种类随机。
// 矩形宽高独立随机范围[28~110]和[28~100],圆形半径[15~68],三角形坐标范围几乎覆盖整个画布,从而位置千变万化。
// 代码完全实现需求: 点击canvas,随机绘制圆/矩形/三角形,每次位置大小不同。
// 未限制绘制数量,可无限点击累积形状,支持清空。
})();
</script>
</body>
</html>
更多推荐



所有评论(0)