云计算百科
云计算领域专业知识百科平台

小白也能画出炫酷六角星:Canvas实战+源码直接抄

小白也能画出炫酷六角星:Canvas实战+源码直接抄

  • 小白也能画出炫酷六角星:Canvas实战+源码直接抄
    • 为啥突然要画六角星?别问,问就是UI设计师又整活了
    • Canvas 是啥玩意儿?先搞明白再动手
    • 六角星到底长啥样?数学原理简单说两句
    • 从零开始手搓一个基础六角星
    • 想换个风格?试试这些花里胡哨的样式
      • 1. 渐变填充
      • 2. 阴影发光
      • 3. 镂空效果
    • 动态绘制?让六角星转起来、缩放、甚至呼吸
      • 旋转 + 缩放
      • 呼吸灯
      • 组合技:旋转 + 呼吸 + 变色
    • 性能别翻车:批量画几十个六角星怎么办?
      • 离屏缓存模板
    • 调试时星星画歪了?常见翻车现场和自救指南
    • 开发老鸟私藏技巧:封装成组件、支持配置、还能导出图片
    • 附赠可直接跑的完整源码(带注释版)

小白也能画出炫酷六角星:Canvas实战+源码直接抄

为啥突然要画六角星?别问,问就是UI设计师又整活了

凌晨一点半,我刚把热乎的 React 打包上线,产品经理甩来一张图:“哥,明天上线,就要这个会转的六角星,带呼吸灯,能导出 PNG,最好还能批量复制,性能别卡,颜色要可配置,谢谢。” 我盯着屏幕,脑子里只有一句话:我谢谢你全家。

可吐槽归吐槽,饭还是要吃。于是我把咖啡续上,打开 VSCode,决定把这次踩坑的全过程写下来,顺带把能复用的代码一股脑儿打包给你。复制粘贴就能跑,注释比我高中时抄的周杰伦歌词还密,谁再说 Canvas 难,就把这篇文章糊他脸上。


Canvas 是啥玩意儿?先搞明白再动手

有些教程一上来就让你 getContext('2d'),结果你连画布长啥样都没看清,就稀里糊涂开始画线,画完发现——咦,怎么是上下颠倒的?别问我怎么知道,我第一次画箭头,结果箭头冲着我自己,仿佛在嘲笑:你行你上啊。

说白了,Canvas 就是一块“透明胶片”,你拿支 JS 做的“笔”在上面乱涂。涂错了只能整块擦掉重画,没有“撤回”按钮,像极了人生。 它有两个尺寸:一个是元素本身大小(width/height 属性),另一个是 CSS 大小。如果你把两者搞混,就会出现“我明明画的 100px,怎么变 150px 了”的灵异事件。记住一句话:属性尺寸是画布像素,CSS 尺寸只是拉伸显示。 所以第一步,先锁死画布,别让浏览器乱缩放:

<canvas id="starCanvas" width="400" height="400"></canvas>
<style>
/* 千万别在这里写 width/height,不然你会哭 */
canvas {
border: 1px solid #ddd;
display: block;
margin: 0 auto;
}
</style>


六角星到底长啥样?数学原理简单说两句

小时候我以为星星都是五角的,直到 UI 妹子给我甩了这张图,我才发现自己天真得可爱。 六角星其实就是两个等边三角形,一个正着,一个倒着,叠在一起,像两个披萨切片交叉。 把圆周六等分,就能得到六个顶点。第一个三角形取第 0、2、4 个点,第二个三角形取第 1、3、5 个点,完事儿。 角度怎么算?别被“弧度”吓到,一句话:弧度 = 角度 * Math.PI / 180。 如果你非要用角度,也随你,反正 Canvas 只认弧度,就像后端只认 JSON,你丢 XML 过去,他内心是崩溃的。


从零开始手搓一个基础六角星

先把画布抢过来:

const canvas = document.getElementById('starCanvas');
const ctx = canvas.getContext('2d');

接下来封装一个“画六角星”函数,参数想多细就多细,反正以后老板说要改颜色,你改一行数字就行,不用连夜加班:

/**
* 画一个六角星
* @param {CanvasRenderingContext2D} ctx – 画布上下文
* @param {number} cx – 中心 x
* @param {number} cy – 中心 y
* @param {number} outerRadius – 外圈半径(尖角)
* @param {number} [innerRatio=0.5] – 内圈半径比例,越小越瘦
* @param {string} [fillStyle='#f39c12'] – 填充色
* @param {string} [strokeStyle='#fff'] – 描边色
* @param {number} [lineWidth=2] – 线宽
*/

function drawSixStar(
ctx,
cx,
cy,
outerRadius,
innerRatio = 0.5,
fillStyle = '#f39c12',
strokeStyle = '#fff',
lineWidth = 2
) {
const angleStep = Math.PI / 3; // 60°
ctx.save(); // 保存上下文,防止污染
ctx.translate(cx, cy); // 把坐标系原点移到星星中心,后面好算
ctx.beginPath();
for (let i = 0; i < 6; i++) {
// 外角尖
const outerAngle = i * angleStep Math.PI / 2; // 从正上方开始,好看
const x1 = Math.cos(outerAngle) * outerRadius;
const y1 = Math.sin(outerAngle) * outerRadius;
// 内角凹
const innerAngle = outerAngle + angleStep / 2;
const x2 = Math.cos(innerAngle) * outerRadius * innerRatio;
const y2 = Math.sin(innerAngle) * outerRadius * innerRatio;
if (i === 0) ctx.moveTo(x1, y1);
else ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
}
ctx.closePath();
// 填充 & 描边
ctx.fillStyle = fillStyle;
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.fill();
ctx.stroke();
ctx.restore(); // 把状态弹回去,别影响别人
}

调用一下,看看效果:

drawSixStar(ctx, 200, 200, 80, 0.5, '#f39c12', '#fff', 2);

跑起来,一颗黄灿灿的六角星就躺在画布中央,像极了你第一次跑通 Hello World 时的激动。 如果你想画个“瘦星”,就把 innerRatio 调到 0.3;想画个“胖星”,就拉到 0.7,像极了我疫情期间的体重变化。


想换个风格?试试这些花里胡哨的样式

1. 渐变填充

CSS 里写 linear-gradient 很爽,Canvas 里也不差:

const grad = ctx.createLinearGradient(80, 80, 80, 80);
grad.addColorStop(0, '#ffecd2');
grad.addColorStop(1, '#fcb69f');
drawSixStar(ctx, 200, 200, 80, 0.5, grad);

2. 阴影发光

别再用 box-shadow 了,Canvas 里叫 shadowBlur,记得画完关掉,不然全场都是阿凡达:

ctx.save();
ctx.shadowColor = 'rgba(255, 100, 200, 0.8)';
ctx.shadowBlur = 20;
drawSixStar(ctx, 200, 200, 80);
ctx.restore(); // 用完立刻关,别像冰箱门不关被妈骂

3. 镂空效果

想要“描边空心”?把 fill() 删了,只留 stroke(),再调粗线宽,瞬间高冷风:

drawSixStar(ctx, 200, 200, 80, 0.5, 'transparent', '#000', 6);


动态绘制?让六角星转起来、缩放、甚至呼吸

静态图老板看不上,非要“动画”。行,给他整一个。

旋转 + 缩放

核心就是 requestAnimationFrame + clearRect + 改 rotate 和 scale。 先整一个“旋转木马”版:

let rotation = 0;
function animate() {
const cx = 200;
const cy = 200;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(cx, cy);
ctx.rotate(rotation);
// 画星星
drawSixStar(ctx, 0, 0, 60, 0.5, '#f39c12');
ctx.restore();
rotation += 0.02;
requestAnimationFrame(animate);
}
animate();

呼吸灯

把半径做成正弦波动,像熬夜程序员的心跳:

let frame = 0;
function breathe() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const scale = 1 + Math.sin(frame * 0.05) * 0.1; // 0.9 ~ 1.1
drawSixStar(ctx, 200, 200, 60 * scale);
frame++;
requestAnimationFrame(breathe);
}
breathe();

组合技:旋转 + 呼吸 + 变色

把上面代码揉一起,再加个 HSL 色相漂移,老板看了直呼“赛博朋克”:

let hue = 0;
function combo() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate(200, 200);
ctx.rotate(rotation);
const scale = 1 + Math.sin(frame * 0.05) * 0.1;
const color = `hsl(${hue}, 80%, 60%)`;
drawSixStar(ctx, 0, 0, 60 * scale, 0.5, color);
ctx.restore();
rotation += 0.02;
frame++;
hue = (hue + 1) % 360;
requestAnimationFrame(combo);
}
combo();


性能别翻车:批量画几十个六角星怎么办?

老板突然说:“一颗不够,我要一屏,最好 60fps,手机也不能卡。” 你心里骂娘,手上还是得写。 记住三句话:

  • 别每帧 new 对象,GC 会杀你。
  • 离屏缓存,画一次,复制无数。
  • 只重绘变化区域,全屏 clearRect 是奢侈。
  • 离屏缓存模板

    // 1. 离屏 canvas,内存里画好
    const off = document.createElement('canvas');
    off.width = off.height = 120; // 比星星大一点
    const offCtx = off.getContext('2d');
    drawSixStar(offCtx, 60, 60, 50, 0.5, '#f39c12'); // 画一次
    // 2. 主屏只负责 drawImage
    const stars = Array.from({ length: 50 }, () => ({
    x: Math.random() * canvas.width,
    y: Math.random() * canvas.height,
    vx: (Math.random() 0.5) * 2,
    vy: (Math.random() 0.5) * 2,
    scale: 0.5 + Math.random() * 0.5,
    rotation: Math.random() * Math.PI * 2
    }));
    function move() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    stars.forEach(s => {
    s.x += s.vx;
    s.y += s.vy;
    if (s.x < 0 || s.x > canvas.width) s.vx *= 1;
    if (s.y < 0 || s.y > canvas.height) s.vy *= 1;
    ctx.save();
    ctx.translate(s.x, s.y);
    ctx.rotate(s.rotation);
    ctx.scale(s.scale, s.scale);
    ctx.drawImage(off, 60, 60); // 把离屏 canvas 当素材贴过来
    ctx.restore();
    });
    requestAnimationFrame(move);
    }
    move();

    上面这段,50 颗星星,手机老年代步机都能跑 60fps。 如果你再狠一点,可以用 requestIdleCallback 分帧更新位置,或者上 WebGL,但那就属于“卷王”领域了,本文不卷。


    调试时星星画歪了?常见翻车现场和自救指南

  • 坐标系上下颠倒 你发现星星倒着长,八成是 translate 之后忘了 rotate 方向,或者 Math.sin 用反了。Canvas 的 Y 轴向下,不是向上,记得初中数学老师的忠告吗?

  • 路径没闭合 少写了 closePath(),结果星星缺一条边,像被老鼠啃了一口。 解决:画完 lineTo 最后回到起点,或者直接 closePath(),一键回家。

  • 角度单位用错 rotate(30) 你以为 30°,实际上 Canvas 直接当成 30 弧度,星星转得比风扇还快。 解决:统一用 angle * Math.PI / 180,或者写个工具函数:

    const DEG = d => d * Math.PI / 180;
    ctx.rotate(DEG(30));

  • 状态污染 你画完星星,下一帧整个画布都带闪粉,原因是 shadowBlur 没关。 解决:永远成对出现 save() 和 restore(),像穿袜子,别只穿一只。


  • 开发老鸟私藏技巧:封装成组件、支持配置、还能导出图片

    写到这儿,你已经可以画星星、做动画、防性能翻车。但老板又提需求:“能不能像 Ant Design 一样,一句 <star color="#f00" size="100" spin /> 就能用?” 行,给他整一个 Web Component,不用 React,不用 Vue,原生就能跑:

    class HexStar extends HTMLElement {
    static get observedAttributes() {
    return ['size', 'color', 'spin', 'export'];
    }
    constructor() {
    super();
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.appendChild(this.canvas);
    }
    connectedCallback() {
    this.render();
    if (this.hasAttribute('spin')) this.startSpin();
    }
    attributeChangedCallback() {
    this.render();
    }
    render() {
    const size = parseInt(this.getAttribute('size') || '100', 10);
    this.canvas.width = this.canvas.height = size;
    const color = this.getAttribute('color') || '#f39c12';
    drawSixStar(this.ctx, size / 2, size / 2, size * 0.4, 0.5, color);
    }
    startSpin() {
    let r = 0;
    const spin = () => {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.save();
    this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2);
    this.ctx.rotate(r);
    const color = this.getAttribute('color') || '#f39c12';
    drawSixStar(this.ctx, 0, 0, this.canvas.width * 0.4, 0.5, color);
    this.ctx.restore();
    r += 0.02;
    requestAnimationFrame(spin);
    };
    spin();
    }
    // 导出 PNG,老板最爱
    exportPNG() {
    return this.canvas.toDataURL('image/png');
    }
    }
    customElements.define('hex-star', HexStar);

    用的时候,跟写 HTML 一样随意:

    <hex-star size="200" color="#ff0066" spin export></hex-star>
    <script>
    const star = document.querySelector('hex-star');
    // 三秒后下载
    setTimeout(() => {
    const link = document.createElement('a');
    link.download = 'star.png';
    link.href = star.exportPNG();
    link.click();
    }, 3000);
    </script>

    封装完,你可以:

    • 传参调颜色、大小、动画;
    • 一键导出 PNG,发给设计师做 PPT;
    • 直接丢 CDN,全公司复用,年终总结写“沉淀通用组件”,绩效稳了。

    附赠可直接跑的完整源码(带注释版)

    下面这份代码,复制粘贴到本地 index.html,双击即可跑。注释比我大学时抄的高数笔记还细,谁再说看不懂,就把电脑送他:

    <!doctype html>
    <html lang="zh-cn">
    <head>
    <meta charset="utf-8">
    <title>六角星全家桶</title>
    <style>
    body { background: #f7f7f7; display: flex; flex-direction: column; align-items: center; }
    canvas { background: #fff; margin: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.1); }
    button { margin: 10px; padding: 6px 12px; }
    </style>
    </head>
    <body>
    <canvas id="stage" width="400" height="400"></canvas>
    <button id="exportBtn">导出 PNG</button>

    <script>
    // 工具:角度转弧度
    const DEG = d => d * Math.PI / 180;

    // 画六角星函数,超全参数版
    function drawSixStar(ctx, cx, cy, outerR, innerRatio = 0.5, fill, stroke, lineW) {
    const step = Math.PI / 3;
    ctx.save();
    ctx.translate(cx, cy);
    ctx.beginPath();
    for (let i = 0; i < 6; i++) {
    const outerA = i * step Math.PI / 2; // 从头顶开始
    const x1 = Math.cos(outerA) * outerR;
    const y1 = Math.sin(outerA) * outerR;
    const innerA = outerA + step / 2;
    const x2 = Math.cos(innerA) * outerR * innerRatio;
    const y2 = Math.sin(innerA) * outerR * innerRatio;
    if (i === 0) ctx.moveTo(x1, y1);
    else ctx.lineTo(x1, y1);
    ctx.lineTo(x2, y2);
    }
    ctx.closePath();
    if (fill) { ctx.fillStyle = fill; ctx.fill(); }
    if (stroke) { ctx.strokeStyle = stroke; ctx.lineWidth = lineW; ctx.stroke(); }
    ctx.restore();
    }

    // 动画:旋转 + 呼吸 + 色相漂移
    const canvas = document.getElementById('stage');
    const ctx = canvas.getContext('2d');
    let frame = 0;
    function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const scale = 1 + Math.sin(frame * 0.05) * 0.1;
    const hue = frame % 360;
    const color = `hsl(${hue}, 80%, 60%)`;
    ctx.save();
    ctx.translate(200, 200);
    ctx.rotate(DEG(frame * 0.5));
    drawSixStar(ctx, 0, 0, 60 * scale, 0.5, color, '#fff', 2);
    ctx.restore();
    frame++;
    requestAnimationFrame(animate);
    }
    animate();

    // 导出 PNG
    document.getElementById('exportBtn').onclick = () => {
    const a = document.createElement('a');
    a.download = 'hexStar.png';
    a.href = canvas.toDataURL('image/png');
    a.click();
    };
    </script>
    </body>
    </html>


    写到这里,天已经蒙蒙亮,咖啡凉了,但星星在屏幕里转得正欢。 我把这份代码和踩坑笔记一并交到你手里,下次产品经理再提“炫酷六角星”,你直接把文件甩过去,附带一句:“跑不通算我输。” 然后合上电脑,去楼下买份热豆浆,让星星自己转去吧。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 小白也能画出炫酷六角星:Canvas实战+源码直接抄
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!